diff --git a/.idea/$CACHE_FILE$ b/.idea/$CACHE_FILE$ new file mode 100644 index 0000000..5df798b --- /dev/null +++ b/.idea/$CACHE_FILE$ @@ -0,0 +1,219 @@ + + + + + + + + + Android + + + Assignment issuesJava + + + C/C++ + + + Class structureJava + + + Code maturityJava + + + Code style issuesJava + + + ComplianceLintAndroid + + + Control flow issuesGroovy + + + Control flow issuesJava + + + CorrectnessLintAndroid + + + Dart + + + Data flow analysisC/C++ + + + Data flowGroovy + + + Data flowJava + + + Declaration redundancyGroovy + + + Declaration redundancyJava + + + EditorConfig + + + EncapsulationJava + + + Error handlingJava + + + Flutter + + + General + + + GeneralC/C++ + + + Google Cloud Endpoints + + + Groovy + + + HTML + + + IconsUsabilityLintAndroid + + + InitializationJava + + + Internationalization + + + InternationalizationJava + + + InternationalizationLintAndroid + + + InteroperabilityLintAndroid + + + J2ME issuesJava + + + JSON and JSON5 + + + JUnitJava + + + Java + + + Java 8Java language level migration aidsJava + + + Java interop issuesKotlin + + + Java language level migration aidsJava + + + JavadocJava + + + Kotlin + + + Kotlin InteroperabilityInteroperabilityLintAndroid + + + LintAndroid + + + MessagesCorrectnessLintAndroid + + + Method metricsJava + + + MigrationKotlin + + + Naming conventionsJava + + + Numeric issuesJava + + + Pattern Validation + + + PerformanceJava + + + PerformanceLintAndroid + + + PortabilityJava + + + Probable bugsJava + + + Probable bugsKotlin + + + Redundant constructsKotlin + + + RegExp + + + Resource managementJava + + + SecurityJava + + + SecurityLintAndroid + + + Serialization issuesJava + + + Style issuesKotlin + + + TestNGJava + + + Threading issuesGroovy + + + Threading issuesJava + + + TypographyUsabilityLintAndroid + + + UsabilityLintAndroid + + + Verbose or redundant code constructsJava + + + VisibilityJava + + + XML + + + YAML + + + + + + \ No newline at end of file diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..5fbbf77 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +VideoTrimmerApplication \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..88ea3aa --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,122 @@ + + + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..077c986 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..75d1db0 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7bfef59 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index f15ba2f..f759553 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # Video-Trimmer-Android -Trim the video by adjusting starting point and ending point in Android +Trim the video by adjusting starting point and ending point in Android. \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..2fe12d2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,56 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + applicationId "com.ahmedbadereldin.videotrimmerapplication" + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation 'com.intuit.sdp:sdp-android:1.0.6' + implementation 'com.intuit.ssp:ssp-android:1.0.6' + + implementation 'com.googlecode.mp4parser:isoparser:1.1.22' + implementation 'com.github.danylovolokh:video-player-manager:0.2.0' + implementation 'com.github.fccaikai:AndroidPermissionX:1.0.0' + + implementation 'iam.void.mediapicker:mediapicker:0.2.3' + implementation 'io.reactivex:rxjava:1.3.5' + implementation 'io.reactivex:rxandroid:1.2.1' + + implementation('com.github.bumptech.glide:glide:4.11.0') { + exclude group: "com.android.support" + } + annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/ahmedbadereldin/videotrimmerapplication/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/ahmedbadereldin/videotrimmerapplication/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..77108c2 --- /dev/null +++ b/app/src/androidTest/java/com/ahmedbadereldin/videotrimmerapplication/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.ahmedbadereldin.videotrimmerapplication + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.ahmedbadereldin.videotrimmerapplication", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c08c6a2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/MainActivity.kt b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/MainActivity.kt new file mode 100644 index 0000000..8dd9b10 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/MainActivity.kt @@ -0,0 +1,19 @@ +package com.ahmedbadereldin.videotrimmerapplication + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.widget.TextView +import com.ahmedbadereldin.videotrimmerapplication.javaCode.NewPostActivity +import kotlinx.android.synthetic.main.activity_main.* + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + btnClick.setOnClickListener { + startActivity(Intent(this, NewPostActivity::class.java)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/FileUtil.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/FileUtil.java new file mode 100644 index 0000000..e992891 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/FileUtil.java @@ -0,0 +1,189 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.OpenableColumns; +import android.text.TextUtils; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class FileUtil { + private static final int EOF = -1; + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + // By default, Android doesn't provide support for JSON + public static final String MIME_TYPE_JSON = "application/json"; + + private FileUtil() { + + } + + public static File from(Context context, Uri uri) throws IOException { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + String fileName = getFileName(context, uri); + String[] splitName = splitFileName(fileName); + File tempFile = File.createTempFile(splitName[0], splitName[1]); + tempFile = rename(tempFile, fileName); + tempFile.deleteOnExit(); + FileOutputStream out = null; + try { + out = new FileOutputStream(tempFile); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + if (inputStream != null) { + copy(inputStream, out); + inputStream.close(); + } + + if (out != null) { + out.close(); + } + return tempFile; + } + + private static String[] splitFileName(String fileName) { + String name = fileName; + String extension = ""; + int i = fileName.lastIndexOf("."); + if (i != -1) { + name = fileName.substring(0, i); + extension = fileName.substring(i); + } + + return new String[]{name, extension}; + } + + private static String getFileName(Context context, Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf(File.separator); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; + } + + private static File rename(File file, String newName) { + File newFile = new File(file.getParent(), newName); + if (!newFile.equals(file)) { + if (newFile.exists() && newFile.delete()) { + Log.d("FileUtil", "Delete old " + newName + " file"); + } + if (file.renameTo(newFile)) { + Log.d("FileUtil", "Rename file to " + newName); + } + } + return newFile; + } + + private static long copy(InputStream input, OutputStream output) throws IOException { + long count = 0; + int n; + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + @Nullable + public static String getMimeType(@NonNull Context context, @NonNull Uri uri) { + + String mimeType = null; + if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + ContentResolver cr = context.getContentResolver(); + mimeType = cr.getType(uri); + } else { + String fileExtension = getExtension(uri.toString()); + + if(fileExtension == null){ + return null; + } + + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + fileExtension.toLowerCase()); + + if(mimeType == null){ + // Handle the misc file extensions + return handleMiscFileExtensions(fileExtension); + } + } + return mimeType; + } + + @Nullable + private static String getExtension(@Nullable String fileName){ + + if(fileName == null || TextUtils.isEmpty(fileName)){ + return null; + } + + char[] arrayOfFilename = fileName.toCharArray(); + for(int i = arrayOfFilename.length-1; i > 0; i--){ + if(arrayOfFilename[i] == '.'){ + return fileName.substring(i+1, fileName.length()); + } + } + return null; + } + + @Nullable + private static String handleMiscFileExtensions(@NonNull String extension){ + + if(extension.equals("json")){ + return MIME_TYPE_JSON; + } + else{ + return null; + } + } + + public static String getMimeType(File file) { + String mimeType = ""; + String extension = getExtention(file.getName()); + if (MimeTypeMap.getSingleton().hasExtension(extension)) { + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + return mimeType; + } + + private static String getExtention(String fileName){ + char[] arrayOfFilename = fileName.toCharArray(); + for(int i = arrayOfFilename.length-1; i > 0; i--){ + if(arrayOfFilename[i] == '.'){ + return fileName.substring(i+1, fileName.length()); + } + } + return ""; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/NewPostActivity.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/NewPostActivity.java new file mode 100644 index 0000000..640aa37 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/NewPostActivity.java @@ -0,0 +1,359 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.webkit.MimeTypeMap; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; +import com.ahmedbadereldin.videotrimmerapplication.R; +import com.bumptech.glide.Glide; +import com.kcode.permissionslib.main.OnRequestPermissionsCallBack; +import com.kcode.permissionslib.main.PermissionCompat; +import java.io.File; +import java.util.Objects; +import iam.thevoid.mediapicker.rxmediapicker.Purpose; +import iam.thevoid.mediapicker.rxmediapicker.RxMediaPicker; +import rx.functions.Action1; + + +public class NewPostActivity extends AppCompatActivity { + + private EditText contentTxt; + private ImageView videoBtn; + private FrameLayout postImgLY; + private ImageView postImg; + private ImageView cancelImgBtn; + Uri uriPostImg; + String pathPostImg; + private static final int REQUEST_TAKE_GALLERY_VIDEO = 100; + private static final int VIDEO_TRIM = 101; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_new_post); + initViews(); + setSharedInentData(getIntent()); + initClicks(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + setSharedInentData(intent); + } + + private void initViews() { + contentTxt = findViewById(R.id.contentTxt); + videoBtn = findViewById(R.id.videoBtn); + postImgLY = findViewById(R.id.postImgLY); + postImg = findViewById(R.id.postImg); + cancelImgBtn = findViewById(R.id.cancelImgBtn); + } + + + private void setSharedInentData(Intent sharedInentData) { + + String receivedAction = sharedInentData.getAction(); + String receivedType = sharedInentData.getType(); + + if (receivedAction != null && receivedAction.equals(Intent.ACTION_SEND)) { + //content is being shared + + if (receivedType.startsWith("text/")) { + //handle sent text + String receivedText = sharedInentData.getStringExtra(Intent.EXTRA_TEXT); + contentTxt.setText(receivedText); + } else if (receivedType.startsWith("image/")) { + //handle sent image + uriPostImg = sharedInentData.getParcelableExtra(Intent.EXTRA_STREAM); + if (uriPostImg != null) { + hideSoftKeyboard(this, 0); + + pathPostImg = null; + Glide.with(this) + .load(uriPostImg) + .into(postImg); + postImgLY.setVisibility(View.VISIBLE); + + } + } else if (receivedType.startsWith("video/")) { +// GlobalData.Toast("video"); + //handle sent video + uriPostImg = sharedInentData.getParcelableExtra(Intent.EXTRA_STREAM); + if (uriPostImg != null) { + //set the video + //RESAMPLE YOUR IMAGE DATA BEFORE DISPLAYING + Glide.with(this) + .load(uriPostImg) + .into(postImg); + postImgLY.setVisibility(View.VISIBLE); + } + } + } + + } + + private void initClicks() { + + cancelImgBtn.setOnClickListener(view -> { + uriPostImg = null; + pathPostImg = null; + postImgLY.setVisibility(View.GONE); + }); + + videoBtn.setOnClickListener(view -> { + try { + + PermissionCompat.Builder builder = new PermissionCompat.Builder(this); + builder.addPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}); + builder.addPermissionRationale(getString(R.string.should_allow_permission)); + builder.addRequestPermissionsCallBack(new OnRequestPermissionsCallBack() { + @Override + public void onGrant() { + RxMediaPicker.builder(NewPostActivity.this) + .pick(Purpose.Pick.VIDEO) + .take(Purpose.Take.VIDEO) + .build() + .subscribe(new Action1() { + @Override + public void call(Uri uri) { + loadIage(uri); + } + }); + } + + @Override + public void onDenied(String permission) { + Toast.makeText(NewPostActivity.this, getString(R.string.some_permission_denied), Toast.LENGTH_SHORT).show(); + } + }); + builder.build().request(); + + } catch (Exception e) { + e.printStackTrace(); + } + + + }); + } + + private void loadIage(Uri filepath) { + Log.d("loadIage", "loadIage: " + filepath); + + // MEDIA GALLERY + String path = getPath(filepath); + Uri filea = Uri.fromFile(new File(path)); + String fileExt = MimeTypeMap.getFileExtensionFromUrl(filea.toString()); + Log.d("onActivityResultaaa", "onActivityResult: " + fileExt); + + if (fileExt.equalsIgnoreCase("MP4")) { + File file = new File(path); + if (file.exists()) { + startActivityForResult(new Intent(NewPostActivity.this, VideoTrimmerActivity.class).putExtra("EXTRA_PATH", path), VIDEO_TRIM); + overridePendingTransition(0, 0); + } else { + Toast.makeText(NewPostActivity.this, "Please select proper video", Toast.LENGTH_LONG).show(); + } + } else { + Toast.makeText(this, getString(R.string.file_format) + " ," + fileExt, Toast.LENGTH_SHORT).show(); + } + + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_TAKE_GALLERY_VIDEO) { + if (resultCode == RESULT_OK) { + Uri selectedImageUri = data.getData(); + + // MEDIA GALLERY + String path = getPath(selectedImageUri); + Uri filea = Uri.fromFile(new File(path)); + String fileExt = MimeTypeMap.getFileExtensionFromUrl(filea.toString()); + Log.d("onActivityResultaaa", "onActivityResult: " + fileExt); + + if (fileExt.equalsIgnoreCase("MP4")) { + File file = new File(path); + if (file.exists()) { + startActivityForResult(new Intent(NewPostActivity.this, VideoTrimmerActivity.class).putExtra("EXTRA_PATH", path), VIDEO_TRIM); + overridePendingTransition(0, 0); + } else { + Toast.makeText(NewPostActivity.this, "Please select proper video", Toast.LENGTH_LONG).show(); + } + } else { + Toast.makeText(this, getString(R.string.file_format) + " ," + fileExt, Toast.LENGTH_SHORT).show(); + } + } + } else if (requestCode == VIDEO_TRIM) { + if (resultCode == RESULT_OK) { + if (data != null) { + String videoPath = data.getExtras().getString("INTENT_VIDEO_FILE"); + File file = new File(videoPath); + Log.d("onActivityResultAA", "onActivityResult: " + file.length()); + + pathPostImg = videoPath; + + Glide.with(this) + .load(pathPostImg) + .into(postImg); + postImgLY.setVisibility(View.VISIBLE); + + } + } + } + + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private String getPath(Uri uri) { + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(this, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(this, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(this, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(this, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return ""; + } + + private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; + try { + int currentApiVersion = Build.VERSION.SDK_INT; + //TODO changes to solve gallery video issue + if (currentApiVersion > Build.VERSION_CODES.M && uri.toString().contains(getString(R.string.app_provider))) { + cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (cursor.getString(column_index) != null) { + String state = Environment.getExternalStorageState(); + File file; + if (Environment.MEDIA_MOUNTED.equals(state)) { + file = new File(Environment.getExternalStorageDirectory() + "/DCIM/", cursor.getString(column_index)); + } else { + file = new File(context.getFilesDir(), cursor.getString(column_index)); + } + return file.getAbsolutePath(); + } + return ""; + } + } else { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + if (cursor.getString(column_index) != null) { + return cursor.getString(column_index); + } + return ""; + } + } + return null; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) + cursor.close(); + } + return ""; + } + + private boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + private boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + private boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + public static void hideSoftKeyboard(Activity activity, int show) { + try { + InputMethodManager inputMethodManager = + (InputMethodManager) activity.getSystemService( + Activity.INPUT_METHOD_SERVICE); + assert inputMethodManager != null; + inputMethodManager.hideSoftInputFromWindow( + Objects.requireNonNull(activity.getCurrentFocus()).getWindowToken(), 0); + } catch (Exception e) { +// e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/Utility.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/Utility.java new file mode 100644 index 0000000..1e56d07 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/Utility.java @@ -0,0 +1,114 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode; + +import android.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews.OnVideoTrimListener; +import com.coremedia.iso.boxes.Container; +import com.googlecode.mp4parser.FileDataSourceViaHeapImpl; +import com.googlecode.mp4parser.authoring.Movie; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder; +import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator; +import com.googlecode.mp4parser.authoring.tracks.AppendTrack; +import com.googlecode.mp4parser.authoring.tracks.CroppedTrack; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +public class Utility { + public static final String VIDEO_FORMAT = ".mp4"; + private static final String TAG = Utility.class.getSimpleName(); + + public static void startTrim(@NonNull File src, @NonNull String dst, long startMs, long endMs, + @NonNull OnVideoTrimListener callback) throws IOException { + final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); + + File file = new File(dst); + file.getParentFile().mkdirs(); + Log.d(TAG, "Generated file path " + dst); + generateVideo(src, file, startMs, endMs, callback); + } + + private static void generateVideo(@NonNull File src, @NonNull File dst, long startMs, + long endMs, @NonNull OnVideoTrimListener callback) throws IOException { + // NOTE: Switched to using FileDataSourceViaHeapImpl since it does not use memory mapping (VM). + // Otherwise we get OOM with large movie files. + Movie movie = MovieCreator.build(new FileDataSourceViaHeapImpl(src.getAbsolutePath())); + + List tracks = movie.getTracks(); + movie.setTracks(new LinkedList()); + // remove all tracks we will create new tracks from the old + + double startTime1 = startMs / 1000; + double endTime1 = endMs / 1000; + + boolean timeCorrected = false; + + // Here we try to find a track that has sync samples. Since we can only start decoding + // at such a sample we SHOULD make sure that the start of the new fragment is exactly + // such a frame + for (Track track : tracks) { + if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) { + if (timeCorrected) { + // This exception here could be a false positive in case we have multiple tracks + // with sync samples at exactly the same positions. E.g. a single movie containing + // multiple qualities of the same video (Microsoft Smooth Streaming file) + + throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported."); + } +// startTime1 = correctTimeToSyncSample(track, startTime1, false); +// endTime1 = correctTimeToSyncSample(track, endTime1, true); + timeCorrected = true; + } + } + + for (Track track : tracks) { + long currentSample = 0; + double currentTime = 0; + double lastTime = -1; + long startSample1 = -1; + long endSample1 = -1; + + for (int i = 0; i < track.getSampleDurations().length; i++) { + long delta = track.getSampleDurations()[i]; + + + if (currentTime > lastTime && currentTime <= startTime1) { + // current sample is still before the new starttime + startSample1 = currentSample; + } + if (currentTime > lastTime && currentTime <= endTime1) { + // current sample is after the new start time and still before the new endtime + endSample1 = currentSample; + } + lastTime = currentTime; + currentTime += (double) delta / (double) track.getTrackMetaData().getTimescale(); + currentSample++; + } + movie.addTrack(new AppendTrack(new CroppedTrack(track, startSample1, endSample1))); + } + + Container out = new DefaultMp4Builder().build(movie); + + FileOutputStream fos = new FileOutputStream(dst); + FileChannel fc = fos.getChannel(); + out.writeContainer(fc); + + fc.close(); + fos.close(); + if (callback != null) + callback.getResult(Uri.parse(dst.toString())); + } + + +} diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/VideoTrimmerActivity.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/VideoTrimmerActivity.java new file mode 100644 index 0000000..df11a93 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/VideoTrimmerActivity.java @@ -0,0 +1,422 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.media.MediaMetadataRetriever; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.VideoView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.ahmedbadereldin.videotrimmerapplication.R; +import com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews.BackgroundTask; +import com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews.BarThumb; +import com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews.CustomRangeSeekBar; +import com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews.OnRangeSeekBarChangeListener; +import com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews.OnVideoTrimListener; +import com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews.TileView; +import java.io.File; +import java.util.Date; +import java.util.Locale; + + +public class VideoTrimmerActivity extends AppCompatActivity implements View.OnClickListener { + + private TextView txtVideoCancel; + private TextView txtVideoUpload; + private TextView txtVideoEditTitle; + private TextView txtVideoTrimSeconds; + private RelativeLayout rlVideoView; + private TileView tileView; + private CustomRangeSeekBar mCustomRangeSeekBarNew; + private VideoView mVideoView; + private ImageView imgPlay; + private SeekBar seekBarVideo; + private TextView txtVideoLength; + + private int mDuration = 0; + private int mTimeVideo = 0; + private int mStartPosition = 0; + private int mEndPosition = 0; + // set your max video trim seconds + private int mMaxDuration = 60; + private Handler mHandler = new Handler(); + + private ProgressDialog mProgressDialog; + String srcFile; + String dstFile; + + OnVideoTrimListener mOnVideoTrimListener = new OnVideoTrimListener() { + @Override + public void onTrimStarted() { + // Create an indeterminate progress dialog + mProgressDialog = new ProgressDialog(VideoTrimmerActivity.this); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.setTitle(getString(R.string.save)); + mProgressDialog.setIndeterminate(true); + mProgressDialog.setCancelable(false); + mProgressDialog.show(); + } + + @Override + public void getResult(Uri uri) { + Log.d("getResult", "getResult: " + uri); + mProgressDialog.dismiss(); + Bundle conData = new Bundle(); + conData.putString("INTENT_VIDEO_FILE", uri.getPath()); + Intent intent = new Intent(); + intent.putExtras(conData); + setResult(RESULT_OK, intent); + finish(); + } + + @Override + public void cancelAction() { + mProgressDialog.dismiss(); + } + + @Override + public void onError(String message) { + mProgressDialog.dismiss(); + } + }; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_video_trim); + + txtVideoCancel = findViewById(R.id.txtVideoCancel); + txtVideoUpload = findViewById(R.id.txtVideoUpload); +// txtVideoEditTitle = (TextView) findViewById(R.id.txtVideoEditTitle); + txtVideoTrimSeconds = findViewById(R.id.txtVideoTrimSeconds); + rlVideoView = findViewById(R.id.llVideoView); + tileView = findViewById(R.id.timeLineView); + mCustomRangeSeekBarNew = findViewById(R.id.timeLineBar); + mVideoView = findViewById(R.id.videoView); + imgPlay = findViewById(R.id.imgPlay); + seekBarVideo = findViewById(R.id.seekBarVideo); + txtVideoLength = findViewById(R.id.txtVideoLength); + + if (getIntent().getExtras() != null) { + srcFile = getIntent().getExtras().getString("EXTRA_PATH"); + } + dstFile = Environment.getExternalStorageDirectory() + "/" + getString(R.string.app_name) + new Date().getTime() + + Utility.VIDEO_FORMAT; + + tileView.post(() -> { + setBitmap(Uri.parse(srcFile)); + mVideoView.setVideoURI(Uri.parse(srcFile)); + }); + + txtVideoCancel.setOnClickListener(this); + txtVideoUpload.setOnClickListener(this); + + mVideoView.setOnPreparedListener(this::onVideoPrepared); + + mVideoView.setOnCompletionListener(mp -> onVideoCompleted()); + + // handle your range seekbar changes + mCustomRangeSeekBarNew.addOnRangeSeekBarListener(new OnRangeSeekBarChangeListener() { + @Override + public void onCreate(CustomRangeSeekBar customRangeSeekBarNew, int index, float value) { + // Do nothing + } + + @Override + public void onSeek(CustomRangeSeekBar customRangeSeekBarNew, int index, float value) { + onSeekThumbs(index, value); + } + + @Override + public void onSeekStart(CustomRangeSeekBar customRangeSeekBarNew, int index, float value) { + if (mVideoView != null) { + mHandler.removeCallbacks(mUpdateTimeTask); + seekBarVideo.setProgress(0); + mVideoView.seekTo(mStartPosition * 1000); + mVideoView.pause(); + imgPlay.setBackgroundResource(R.drawable.ic_white_play); + } + } + + @Override + public void onSeekStop(CustomRangeSeekBar customRangeSeekBarNew, int index, float value) { + onStopSeekThumbs(); + } + }); + + imgPlay.setOnClickListener(this); + + // handle changes on seekbar for video play + seekBarVideo.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (mVideoView != null) { + mHandler.removeCallbacks(mUpdateTimeTask); + seekBarVideo.setMax(mTimeVideo * 1000); + seekBarVideo.setProgress(0); + mVideoView.seekTo(mStartPosition * 1000); + mVideoView.pause(); + imgPlay.setBackgroundResource(R.drawable.ic_white_play); + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mHandler.removeCallbacks(mUpdateTimeTask); + mVideoView.seekTo((mStartPosition * 1000) - seekBarVideo.getProgress()); + } + }); + } + + @Override + public void onClick(View view) { + if (view == txtVideoCancel) { + finish(); + } else if (view == txtVideoUpload) { + int diff = mEndPosition - mStartPosition; + if (diff < 3) { + Toast.makeText(VideoTrimmerActivity.this, getString(R.string.video_length_validation), + Toast.LENGTH_LONG).show(); + } else { + MediaMetadataRetriever + mediaMetadataRetriever = new MediaMetadataRetriever(); + mediaMetadataRetriever.setDataSource(VideoTrimmerActivity.this, Uri.parse(srcFile)); + final File file = new File(srcFile); + Log.d("fileLength", "onClick: " + file.length() + " " + file.canExecute()); + //notify that video trimming started + if (mOnVideoTrimListener != null) + mOnVideoTrimListener.onTrimStarted(); + + BackgroundTask.execute(new BackgroundTask.Task("", 0L, "") { + @Override + public void execute() { + try { + Log.d("executeAAAA", "execute: " + "Aaaa" + file.length() + " " + dstFile + " " + mStartPosition + " " + mEndPosition + " " + mOnVideoTrimListener); + + Utility.startTrim(file, dstFile, mStartPosition * 1000, mEndPosition * 1000, mOnVideoTrimListener); + } catch (final Throwable e) { + Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e); + } + } + } + ); + + + } + + } else if (view == imgPlay) { + if (mVideoView.isPlaying()) { + if (mVideoView != null) { + mVideoView.pause(); + imgPlay.setBackgroundResource(R.drawable.ic_white_play); + } + } else { + if (mVideoView != null) { + mVideoView.start(); + imgPlay.setBackgroundResource(R.drawable.ic_white_pause); + if (seekBarVideo.getProgress() == 0) { + txtVideoLength.setText("00:00"); + updateProgressBar(); + } + } + } + } + } + + private void setBitmap(Uri mVideoUri) { + tileView.setVideo(mVideoUri); + } + + private void onVideoPrepared(@NonNull MediaPlayer mp) { + // Adjust the size of the video + // so it fits on the screen + //TODO manage proportion for video + /*int videoWidth = mp.getVideoWidth(); + int videoHeight = mp.getVideoHeight(); + float videoProportion = (float) videoWidth / (float) videoHeight; + int screenWidth = rlVideoView.getWidth(); + int screenHeight = rlVideoView.getHeight(); + float screenProportion = (float) screenWidth / (float) screenHeight; + ViewGroup.LayoutParams lp = mVideoView.getLayoutParams(); + + if (videoProportion > screenProportion) { + lp.width = screenWidth; + lp.height = (int) ((float) screenWidth / videoProportion); + } else { + lp.width = (int) (videoProportion * (float) screenHeight); + lp.height = screenHeight; + } + mVideoView.setLayoutParams(lp);*/ + + mDuration = mVideoView.getDuration() / 1000; + setSeekBarPosition(); + } + + public void updateProgressBar() { + mHandler.postDelayed(mUpdateTimeTask, 100); + } + + private Runnable mUpdateTimeTask = new Runnable() { + public void run() { + if (seekBarVideo.getProgress() >= seekBarVideo.getMax()) { + seekBarVideo.setProgress((mVideoView.getCurrentPosition() - mStartPosition * 1000)); + txtVideoLength.setText(milliSecondsToTimer(seekBarVideo.getProgress()) + ""); + mVideoView.seekTo(mStartPosition * 1000); + mVideoView.pause(); + seekBarVideo.setProgress(0); + txtVideoLength.setText("00:00"); + imgPlay.setBackgroundResource(R.drawable.ic_white_play); + } else { + seekBarVideo.setProgress((mVideoView.getCurrentPosition() - mStartPosition * 1000)); + txtVideoLength.setText(milliSecondsToTimer(seekBarVideo.getProgress()) + ""); + mHandler.postDelayed(this, 100); + } + } + }; + + private void setSeekBarPosition() { + + if (mDuration >= mMaxDuration) { + mStartPosition = 0; + mEndPosition = mMaxDuration; + + mCustomRangeSeekBarNew.setThumbValue(0, (mStartPosition * 100) / mDuration); + mCustomRangeSeekBarNew.setThumbValue(1, (mEndPosition * 100) / mDuration); + + } else { + mStartPosition = 0; + mEndPosition = mDuration; + } + + + mTimeVideo = mDuration; + mCustomRangeSeekBarNew.initMaxWidth(); + seekBarVideo.setMax(mMaxDuration * 1000); + mVideoView.seekTo(mStartPosition * 1000); + + String mStart = mStartPosition + ""; + if (mStartPosition < 10) + mStart = "0" + mStartPosition; + + int startMin = Integer.parseInt(mStart) / 60; + int startSec = Integer.parseInt(mStart) % 60; + + String mEnd = mEndPosition + ""; + if (mEndPosition < 10) + mEnd = "0" + mEndPosition; + + int endMin = Integer.parseInt(mEnd) / 60; + int endSec = Integer.parseInt(mEnd) % 60; + + txtVideoTrimSeconds.setText(String.format(Locale.US, "%02d:%02d - %02d:%02d", startMin, startSec, endMin, endSec)); + } + + /** + * called when playing video completes + */ + private void onVideoCompleted() { + mHandler.removeCallbacks(mUpdateTimeTask); + seekBarVideo.setProgress(0); + mVideoView.seekTo(mStartPosition * 1000); + mVideoView.pause(); + imgPlay.setBackgroundResource(R.drawable.ic_white_play); + } + + /** + * Handle changes of left and right thumb movements + * + * @param index index of thumb + * @param value value + */ + private void onSeekThumbs(int index, float value) { + switch (index) { + case BarThumb.LEFT: { + mStartPosition = (int) ((mDuration * value) / 100L); + mVideoView.seekTo(mStartPosition * 1000); + break; + } + case BarThumb.RIGHT: { + mEndPosition = (int) ((mDuration * value) / 100L); + break; + } + } + mTimeVideo = (mEndPosition - mStartPosition); + seekBarVideo.setMax(mTimeVideo * 1000); + seekBarVideo.setProgress(0); + mVideoView.seekTo(mStartPosition * 1000); + + String mStart = mStartPosition + ""; + if (mStartPosition < 10) + mStart = "0" + mStartPosition; + + int startMin = Integer.parseInt(mStart) / 60; + int startSec = Integer.parseInt(mStart) % 60; + + String mEnd = mEndPosition + ""; + if (mEndPosition < 10) + mEnd = "0" + mEndPosition; + int endMin = Integer.parseInt(mEnd) / 60; + int endSec = Integer.parseInt(mEnd) % 60; + + txtVideoTrimSeconds.setText(String.format(Locale.US, "%02d:%02d - %02d:%02d", startMin, startSec, endMin, endSec)); + + } + + private void onStopSeekThumbs() { +// mMessageHandler.removeMessages(SHOW_PROGRESS); +// mVideoView.pause(); +// mPlayView.setVisibility(View.VISIBLE); + } + + public String milliSecondsToTimer(long milliseconds) { + String finalTimerString = ""; + String secondsString; + String minutesString; + + + int hours = (int) (milliseconds / (1000 * 60 * 60)); + int minutes = (int) (milliseconds % (1000 * 60 * 60)) / (1000 * 60); + int seconds = (int) ((milliseconds % (1000 * 60 * 60)) % (1000 * 60) / 1000); + // Add hours if there + if (hours > 0) { + finalTimerString = hours + ":"; + } + + // Prepending 0 to seconds if it is one digit + if (seconds < 10) { + secondsString = "0" + seconds; + } else { + secondsString = "" + seconds; + } + + if (minutes < 10) { + minutesString = "0" + minutes; + } else { + minutesString = "" + minutes; + } + + finalTimerString = finalTimerString + minutesString + ":" + secondsString; + + // return timer string + return finalTimerString; + } +} diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/BackgroundTask.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/BackgroundTask.java new file mode 100644 index 0000000..ff12cb8 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/BackgroundTask.java @@ -0,0 +1,247 @@ +// The MIT License (MIT) + +// Copyright (c) 2018 Intuz Solutions Pvt Ltd. + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class BackgroundTask { + + private static final String TAG = "BackgroundTask"; + + public static final Executor DEFAULT_EXECUTOR = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors()); + private static Executor executor = DEFAULT_EXECUTOR; + private static final List TASKS = new ArrayList<>(); + private static final ThreadLocal CURRENT_SERIAL = new ThreadLocal<>(); + + private BackgroundTask() { + } + + /** + * Execute a runnable after the given delay. + * + * @param runnable the task to execute + * @param delay the time from now to delay execution, in milliseconds + *

+ * if delay is strictly positive and the current + * executor does not support scheduling (if + * Executor has been called with such an + * executor) + * @return Future associated to the running task + * @throws IllegalArgumentException if the current executor set by Executor + * does not support scheduling + */ + private static Future directExecute(Runnable runnable, long delay) { + Future future = null; + if (delay > 0) { + /* no serial, but a delay: schedule the task */ + if (!(executor instanceof ScheduledExecutorService)) { + throw new IllegalArgumentException("The executor set does not support scheduling"); + } + ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) executor; + future = scheduledExecutorService.schedule(runnable, delay, TimeUnit.MILLISECONDS); + } else { + if (executor instanceof ExecutorService) { + ExecutorService executorService = (ExecutorService) executor; + future = executorService.submit(runnable); + } else { + /* non-cancellable task */ + executor.execute(runnable); + } + } + return future; + } + + /** + * Execute a task after (at least) its delay and after all + * tasks added with the same non-null serial (if any) have + * completed execution. + * + * @param task the task to execute + * @throws IllegalArgumentException if task.delay is strictly positive and the + * current executor does not support scheduling (if + * Executor has been called with such an + * executor) + */ + public static synchronized void execute(Task task) { + Future future = null; + if (task.serial == null || !hasRunning(task.serial)) { + task.executionAsked = true; + future = directExecute(task, task.remainingDelay); + } + if ((task.id != null || task.serial != null) && !task.managed.get()) { + /* keep task */ + task.future = future; + TASKS.add(task); + } + } + + /** + * Indicates whether a task with the specified serial has been + * submitted to the executor. + * + * @param serial the serial queue + * @return true if such a task has been submitted, + * false otherwise + */ + private static boolean hasRunning(String serial) { + for (Task task : TASKS) { + if (task.executionAsked && serial.equals(task.serial)) { + return true; + } + } + return false; + } + + /** + * Retrieve and remove the first task having the specified + * serial (if any). + * + * @param serial the serial queue + * @return task if found, null otherwise + */ + private static Task take(String serial) { + int len = TASKS.size(); + for (int i = 0; i < len; i++) { + if (serial.equals(TASKS.get(i).serial)) { + return TASKS.remove(i); + } + } + return null; + } + + /** + * Cancel all tasks having the specified id. + * + * @param id the cancellation identifier + * @param mayInterruptIfRunning true if the thread executing this task should be + * interrupted; otherwise, in-progress tasks are allowed to + * complete + */ + public static synchronized void cancelAllTask(String id, boolean mayInterruptIfRunning) { + for (int i = TASKS.size() - 1; i >= 0; i--) { + Task task = TASKS.get(i); + if (id.equals(task.id)) { + if (task.future != null) { + task.future.cancel(mayInterruptIfRunning); + if (!task.managed.getAndSet(true)) { + /* + * the task has been submitted to the executor, but its + * execution has not started yet, so that its run() + * method will never call postExecute() + */ + task.postExecute(); + } + } else if (task.executionAsked) { + Log.w(TAG, "A task with id " + task.id + " cannot be cancelled (the executor set does not support it)"); + } else { + /* this task has not been submitted to the executor */ + TASKS.remove(i); + } + } + } + } + + public static abstract class Task implements Runnable { + + private String id; + private long remainingDelay; + private long targetTimeMillis; /* since epoch */ + private String serial; + private boolean executionAsked; + private Future future; + + /* + * A task can be cancelled after it has been submitted to the executor + * but before its run() method is called. In that case, run() will never + * be called, hence neither will postExecute(): the tasks with the same + * serial identifier (if any) will never be submitted. + * + * Therefore, cancelAllTask() *must* call postExecute() if run() is not + * started. + * + * This flag guarantees that either cancelAllTask() or run() manages this + * task post execution, but not both. + */ + private AtomicBoolean managed = new AtomicBoolean(); + + protected Task(String id, long delay, String serial) { + if (!"".equals(id)) { + this.id = id; + } + if (delay > 0) { + remainingDelay = delay; + targetTimeMillis = System.currentTimeMillis() + delay; + } + if (!"".equals(serial)) { + this.serial = serial; + } + } + + @Override + public void run() { + if (managed.getAndSet(true)) { + /* cancelled and postExecute() already called */ + return; + } + + try { + CURRENT_SERIAL.set(serial); + execute(); + } finally { + /* handle next tasks */ + postExecute(); + } + } + + public abstract void execute(); + + private void postExecute() { + if (id == null && serial == null) { + /* nothing to do */ + return; + } + CURRENT_SERIAL.set(null); + synchronized (BackgroundTask.class) { + /* execution complete */ + TASKS.remove(this); + + if (serial != null) { + Task next = take(serial); + if (next != null) { + if (next.remainingDelay != 0) { + /* the delay may not have elapsed yet */ + next.remainingDelay = Math.max(0L, targetTimeMillis - System.currentTimeMillis()); + } + /* a task having the same serial was queued, execute it */ + BackgroundTask.execute(next); + } + } + } + } + } + + +} + diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/BarThumb.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/BarThumb.java new file mode 100644 index 0000000..4df4139 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/BarThumb.java @@ -0,0 +1,127 @@ +// The MIT License (MIT) + +// Copyright (c) 2018 Intuz Solutions Pvt Ltd. + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import androidx.annotation.NonNull; + +import com.ahmedbadereldin.videotrimmerapplication.R; + +import java.util.List; +import java.util.Vector; + + +public class BarThumb { + + public static final int LEFT = 0; + public static final int RIGHT = 1; + + private int mIndex; + private float mVal; + private float mPos; + private Bitmap mBitmap; + private int mWidthBitmap; + private int mHeightBitmap; + + private float mLastTouchX; + + private BarThumb() { + mVal = 0; + mPos = 0; + } + + public int getIndex() { + return mIndex; + } + + private void setIndex(int index) { + mIndex = index; + } + + public float getVal() { + return mVal; + } + + public void setVal(float val) { + mVal = val; + } + + public float getPos() { + return mPos; + } + + public void setPos(float pos) { + mPos = pos; + } + + public Bitmap getBitmap() { + return mBitmap; + } + + private void setBitmap(@NonNull Bitmap bitmap) { + mBitmap = bitmap; + mWidthBitmap = bitmap.getWidth(); + mHeightBitmap = bitmap.getHeight(); + } + + @NonNull + public static List initThumbs(Resources resources) { + + List barThumbs = new Vector<>(); + + for (int i = 0; i < 2; i++) { + BarThumb th = new BarThumb(); + th.setIndex(i); + if (i == 0) { + int resImageLeft = R.drawable.time_line_a; + th.setBitmap(BitmapFactory.decodeResource(resources, resImageLeft)); + } else { + int resImageRight = R.drawable.time_line_a; + th.setBitmap(BitmapFactory.decodeResource(resources, resImageRight)); + } + + barThumbs.add(th); + } + + return barThumbs; + } + + public static int getWidthBitmap(@NonNull List barThumbs) { + return barThumbs.get(0).getWidthBitmap(); + } + + public static int getHeightBitmap(@NonNull List barThumbs) { + return barThumbs.get(0).getHeightBitmap(); + } + + public float getLastTouchX() { + return mLastTouchX; + } + + public void setLastTouchX(float lastTouchX) { + mLastTouchX = lastTouchX; + } + + public int getWidthBitmap() { + return mWidthBitmap; + } + + private int getHeightBitmap() { + return mHeightBitmap; + } +} diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/CustomRangeSeekBar.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/CustomRangeSeekBar.java new file mode 100644 index 0000000..cf2e1c8 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/CustomRangeSeekBar.java @@ -0,0 +1,380 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.ahmedbadereldin.videotrimmerapplication.R; + +import java.util.ArrayList; +import java.util.List; + + +public class CustomRangeSeekBar extends View { + + private int mHeightTimeLine; + private List mBarThumbs; + private List mListeners; + private float mMaxWidth; + private float mThumbWidth; + private float mThumbHeight; + private int mViewWidth; + private float mPixelRangeMin; + private float mPixelRangeMax; + private float mScaleRangeMax; + private boolean mFirstRun; + + private final Paint mShadow = new Paint(); + private final Paint mLine = new Paint(); + + public CustomRangeSeekBar(@NonNull Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CustomRangeSeekBar(@NonNull Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + mBarThumbs = BarThumb.initThumbs(getResources()); + mThumbWidth = BarThumb.getWidthBitmap(mBarThumbs); + mThumbHeight = BarThumb.getHeightBitmap(mBarThumbs); + + mScaleRangeMax = 100; + mHeightTimeLine = getContext().getResources().getDimensionPixelOffset(R.dimen.frames_video_height); + + setFocusable(true); + setFocusableInTouchMode(true); + + mFirstRun = true; + + int shadowColor = ContextCompat.getColor(getContext(), R.color.shadow_color); + mShadow.setAntiAlias(true); + mShadow.setColor(shadowColor); + mShadow.setAlpha(177); + + int lineColor = ContextCompat.getColor(getContext(), R.color.line_color); + mLine.setAntiAlias(true); + mLine.setColor(lineColor); + mLine.setAlpha(200); + } + + public void initMaxWidth() { + mMaxWidth = mBarThumbs.get(1).getPos() - mBarThumbs.get(0).getPos(); + + onSeekStop(this, 0, mBarThumbs.get(0).getVal()); + onSeekStop(this, 1, mBarThumbs.get(1).getVal()); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int minW = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); + mViewWidth = resolveSizeAndState(minW, widthMeasureSpec, 1); + + int minH = getPaddingBottom() + getPaddingTop() + (int) mThumbHeight; + int viewHeight = resolveSizeAndState(minH, heightMeasureSpec, 1); + + setMeasuredDimension(mViewWidth, viewHeight); + + mPixelRangeMin = 0; + mPixelRangeMax = mViewWidth - mThumbWidth; + + if (mFirstRun) { + for (int i = 0; i < mBarThumbs.size(); i++) { + BarThumb th = mBarThumbs.get(i); + th.setVal(mScaleRangeMax * i); + th.setPos(mPixelRangeMax * i); + } + // Fire listener callback + onCreate(this, currentThumb, getThumbValue(currentThumb)); + mFirstRun = false; + } + } + + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + + drawShadow(canvas); + drawThumbs(canvas); + } + + private int currentThumb = 0; + + @Override + public boolean onTouchEvent(@NonNull MotionEvent ev) { + final BarThumb mBarThumb; + final BarThumb mBarThumb2; + final float coordinate = ev.getX(); + final int action = ev.getAction(); + + switch (action) { + case MotionEvent.ACTION_DOWN: { + // Remember where we started + currentThumb = getClosestThumb(coordinate); + + if (currentThumb == -1) { + return false; + } + + mBarThumb = mBarThumbs.get(currentThumb); + mBarThumb.setLastTouchX(coordinate); + onSeekStart(this, currentThumb, mBarThumb.getVal()); + return true; + } + case MotionEvent.ACTION_UP: { + + if (currentThumb == -1) { + return false; + } + + mBarThumb = mBarThumbs.get(currentThumb); + onSeekStop(this, currentThumb, mBarThumb.getVal()); + return true; + } + + case MotionEvent.ACTION_MOVE: { + mBarThumb = mBarThumbs.get(currentThumb); + mBarThumb2 = mBarThumbs.get(currentThumb == 0 ? 1 : 0); + // Calculate the distance moved + final float dx = coordinate - mBarThumb.getLastTouchX(); + final float newX = mBarThumb.getPos() + dx; + + if (currentThumb == 0) { + + if ((newX + mBarThumb.getWidthBitmap()) >= mBarThumb2.getPos()) { + mBarThumb.setPos(mBarThumb2.getPos() - mBarThumb.getWidthBitmap()); + } else if (newX <= mPixelRangeMin) { + mBarThumb.setPos(mPixelRangeMin); + if ((mBarThumb2.getPos() - (mBarThumb.getPos() + dx)) > mMaxWidth) { + mBarThumb2.setPos(mBarThumb.getPos() + dx + mMaxWidth); + setThumbPos(1, mBarThumb2.getPos()); + } + } else { + //Check if thumb is not out of max width +// checkPositionThumb(mBarThumb, mBarThumb2, dx, true, coordinate); + if ((mBarThumb2.getPos() - (mBarThumb.getPos() + dx)) > mMaxWidth) { + mBarThumb2.setPos(mBarThumb.getPos() + dx + mMaxWidth); + setThumbPos(1, mBarThumb2.getPos()); + } + // Move the object + mBarThumb.setPos(mBarThumb.getPos() + dx); + + // Remember this touch position for the next move event + mBarThumb.setLastTouchX(coordinate); + } + + } else { + if (newX <= mBarThumb2.getPos() + mBarThumb2.getWidthBitmap()) { + mBarThumb.setPos(mBarThumb2.getPos() + mBarThumb.getWidthBitmap()); + } else if (newX >= mPixelRangeMax) { + mBarThumb.setPos(mPixelRangeMax); + if (((mBarThumb.getPos() + dx) - mBarThumb2.getPos()) > mMaxWidth) { + mBarThumb2.setPos(mBarThumb.getPos() + dx - mMaxWidth); + setThumbPos(0, mBarThumb2.getPos()); + } + } else { + //Check if thumb is not out of max width +// checkPositionThumb(mBarThumb2, mBarThumb, dx, false, coordinate); + if (((mBarThumb.getPos() + dx) - mBarThumb2.getPos()) > mMaxWidth) { + mBarThumb2.setPos(mBarThumb.getPos() + dx - mMaxWidth); + setThumbPos(0, mBarThumb2.getPos()); + } + // Move the object + mBarThumb.setPos(mBarThumb.getPos() + dx); + // Remember this touch position for the next move event + mBarThumb.setLastTouchX(coordinate); + } + } + + setThumbPos(currentThumb, mBarThumb.getPos()); + + // Invalidate to request a redraw + invalidate(); + return true; + } + } + return false; + } + + private void checkPositionThumb(@NonNull BarThumb mBarThumbLeft, @NonNull BarThumb mBarThumbRight, float dx, boolean isLeftMove, float coordinate) { + + if (isLeftMove && dx < 0) { + if ((mBarThumbRight.getPos() - (mBarThumbLeft.getPos() + dx)) > mMaxWidth) { + mBarThumbRight.setPos(mBarThumbLeft.getPos() + dx + mMaxWidth); + setThumbPos(1, mBarThumbRight.getPos()); + } + } else if (!isLeftMove && dx > 0) { + if (((mBarThumbRight.getPos() + dx) - mBarThumbLeft.getPos()) > mMaxWidth) { + mBarThumbLeft.setPos(mBarThumbRight.getPos() + dx - mMaxWidth); + setThumbPos(0, mBarThumbLeft.getPos()); + } + } + + } + + + private float pixelToScale(int index, float pixelValue) { + float scale = (pixelValue * 100) / mPixelRangeMax; + if (index == 0) { + float pxThumb = (scale * mThumbWidth) / 100; + return scale + (pxThumb * 100) / mPixelRangeMax; + } else { + float pxThumb = ((100 - scale) * mThumbWidth) / 100; + return scale - (pxThumb * 100) / mPixelRangeMax; + } + } + + private float scaleToPixel(int index, float scaleValue) { + float px = (scaleValue * mPixelRangeMax) / 100; + if (index == 0) { + float pxThumb = (scaleValue * mThumbWidth) / 100; + return px - pxThumb; + } else { + float pxThumb = ((100 - scaleValue) * mThumbWidth) / 100; + return px + pxThumb; + } + } + + private void calculateThumbValue(int index) { + if (index < mBarThumbs.size() && !mBarThumbs.isEmpty()) { + BarThumb th = mBarThumbs.get(index); + th.setVal(pixelToScale(index, th.getPos())); + onSeek(this, index, th.getVal()); + } + } + + private void calculateThumbPos(int index) { + if (index < mBarThumbs.size() && !mBarThumbs.isEmpty()) { + BarThumb th = mBarThumbs.get(index); + th.setPos(scaleToPixel(index, th.getVal())); + } + } + + private float getThumbValue(int index) { + return mBarThumbs.get(index).getVal(); + } + + public void setThumbValue(int index, float value) { + mBarThumbs.get(index).setVal(value); + calculateThumbPos(index); + // Tell the view we want a complete redraw + invalidate(); + } + + private void setThumbPos(int index, float pos) { + mBarThumbs.get(index).setPos(pos); + calculateThumbValue(index); + // Tell the view we want a complete redraw + invalidate(); + } + + private int getClosestThumb(float coordinate) { + int closest = -1; + if (!mBarThumbs.isEmpty()) { + for (int i = 0; i < mBarThumbs.size(); i++) { + // Find thumb closest to x coordinate + final float tcoordinate = mBarThumbs.get(i).getPos() + mThumbWidth; + if (coordinate >= mBarThumbs.get(i).getPos() && coordinate <= tcoordinate) { + closest = mBarThumbs.get(i).getIndex(); + } + } + } + return closest; + } + + private void drawShadow(@NonNull Canvas canvas) { + if (!mBarThumbs.isEmpty()) { + + for (BarThumb th : mBarThumbs) { + if (th.getIndex() == 0) { + final float x = th.getPos(); + if (x > mPixelRangeMin) { + Rect mRect = new Rect(0, (int) (mThumbHeight - mHeightTimeLine) / 2, + (int) (x + (mThumbWidth / 2)), mHeightTimeLine + (int) (mThumbHeight - mHeightTimeLine) / 2); + canvas.drawRect(mRect, mShadow); + } + } else { + final float x = th.getPos(); + if (x < mPixelRangeMax) { + Rect mRect = new Rect((int) (x + (mThumbWidth / 2)), (int) (mThumbHeight - mHeightTimeLine) / 2, + (mViewWidth), mHeightTimeLine + (int) (mThumbHeight - mHeightTimeLine) / 2); + canvas.drawRect(mRect, mShadow); + } + } + } + } + } + + private void drawThumbs(@NonNull Canvas canvas) { + + if (!mBarThumbs.isEmpty()) { + for (BarThumb th : mBarThumbs) { + if (th.getIndex() == 0) { + canvas.drawBitmap(th.getBitmap(), th.getPos() + getPaddingLeft(), getPaddingTop(), null); + } else { + canvas.drawBitmap(th.getBitmap(), th.getPos() - getPaddingRight(), getPaddingTop(), null); + } + } + } + } + + public void addOnRangeSeekBarListener(OnRangeSeekBarChangeListener listener) { + + if (mListeners == null) { + mListeners = new ArrayList<>(); + } + + mListeners.add(listener); + } + + private void onCreate(CustomRangeSeekBar CustomRangeSeekBar, int index, float value) { + if (mListeners == null) + return; + + for (OnRangeSeekBarChangeListener item : mListeners) { + item.onCreate(CustomRangeSeekBar, index, value); + } + } + + private void onSeek(CustomRangeSeekBar CustomRangeSeekBar, int index, float value) { + if (mListeners == null) + return; + + for (OnRangeSeekBarChangeListener item : mListeners) { + item.onSeek(CustomRangeSeekBar, index, value); + } + } + + private void onSeekStart(CustomRangeSeekBar CustomRangeSeekBar, int index, float value) { + if (mListeners == null) + return; + + for (OnRangeSeekBarChangeListener item : mListeners) { + item.onSeekStart(CustomRangeSeekBar, index, value); + } + } + + private void onSeekStop(CustomRangeSeekBar CustomRangeSeekBar, int index, float value) { + if (mListeners == null) + return; + + for (OnRangeSeekBarChangeListener item : mListeners) { + item.onSeekStop(CustomRangeSeekBar, index, value); + } + } + + public List getThumbs() { + return mBarThumbs; + } +} diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/OnRangeSeekBarChangeListener.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/OnRangeSeekBarChangeListener.java new file mode 100644 index 0000000..7be4d8a --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/OnRangeSeekBarChangeListener.java @@ -0,0 +1,10 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews; +public interface OnRangeSeekBarChangeListener { + void onCreate(CustomRangeSeekBar CustomRangeSeekBar, int index, float value); + + void onSeek(CustomRangeSeekBar CustomRangeSeekBar, int index, float value); + + void onSeekStart(CustomRangeSeekBar CustomRangeSeekBar, int index, float value); + + void onSeekStop(CustomRangeSeekBar CustomRangeSeekBar, int index, float value); +} diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/OnVideoTrimListener.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/OnVideoTrimListener.java new file mode 100644 index 0000000..949d6c0 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/OnVideoTrimListener.java @@ -0,0 +1,14 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews; + +import android.net.Uri; + +public interface OnVideoTrimListener { + + void onTrimStarted(); + + void getResult(final Uri uri); + + void cancelAction(); + + void onError(final String message); +} diff --git a/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/TileView.java b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/TileView.java new file mode 100644 index 0000000..1faca51 --- /dev/null +++ b/app/src/main/java/com/ahmedbadereldin/videotrimmerapplication/javaCode/customVideoViews/TileView.java @@ -0,0 +1,236 @@ +package com.ahmedbadereldin.videotrimmerapplication.javaCode.customVideoViews; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.LongSparseArray; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.ahmedbadereldin.videotrimmerapplication.R; + +import java.util.HashMap; +import java.util.Map; + +public class TileView extends View { + + private Uri mVideoUri; + private int mHeightView; + private LongSparseArray mBitmapList = null; + private int viewWidth = 0; + private int viewHeight = 0; + + public TileView(@NonNull Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TileView(@NonNull Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + mHeightView = getContext().getResources().getDimensionPixelOffset(R.dimen.frames_video_height); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int minW = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); + int w = resolveSizeAndState(minW, widthMeasureSpec, 1); + + final int minH = getPaddingBottom() + getPaddingTop() + mHeightView; + int h = resolveSizeAndState(minH, heightMeasureSpec, 1); + + setMeasuredDimension(w, h); + } + + @Override + protected void onSizeChanged(final int w, int h, final int oldW, int oldH) { + super.onSizeChanged(w, h, oldW, oldH); + viewWidth = w; + viewHeight = h; + if (w != oldW) { + if (mVideoUri != null) + getBitmap(); + } + } + + private void getBitmap() { + BackgroundTask + .execute(new BackgroundTask.Task("", 0L, "") { + @Override + public void execute() { + try { + LongSparseArray thumbnailList = new LongSparseArray<>(); + + MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); + mediaMetadataRetriever.setDataSource(getContext(), mVideoUri); + + // Retrieve media data + long videoLengthInMs = Integer.parseInt(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000; + + // Set thumbnail properties (Thumbs are squares) + final int thumbWidth = mHeightView; + final int thumbHeight = mHeightView; + + int numThumbs = (int) Math.ceil(((float) viewWidth) / thumbWidth); + + final long interval = videoLengthInMs / numThumbs; + + for (int i = 0; i < numThumbs; ++i) { + Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(i * interval, MediaMetadataRetriever.OPTION_CLOSEST_SYNC); + // TODO: bitmap might be null here, hence throwing NullPointerException. You were right + try { + bitmap = Bitmap.createScaledBitmap(bitmap, thumbWidth, thumbHeight, false); + } catch (Exception e) { + e.printStackTrace(); + } + thumbnailList.put(i, bitmap); + } + + mediaMetadataRetriever.release(); + returnBitmaps(thumbnailList); + } catch (final Throwable e) { + Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e); + } + } + } + ); + } + + private void returnBitmaps(final LongSparseArray thumbnailList) { + new MainThreadExecutor().runTask("", new Runnable() { + @Override + public void run() { + mBitmapList = thumbnailList; + invalidate(); + } + } + , 0L); + } + + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + + if (mBitmapList != null) { + canvas.save(); + int x = 0; + + for (int i = 0; i < mBitmapList.size(); i++) { + Bitmap bitmap = mBitmapList.get(i); + + if (bitmap != null) { + canvas.drawBitmap(bitmap, x, 0, null); + x = x + bitmap.getWidth(); + } + } + } + } + + public void setVideo(@NonNull Uri data) { + mVideoUri = data; + getBitmap(); + } + + public final class MainThreadExecutor { + + private final Handler HANDLER = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + Runnable callback = msg.getCallback(); + if (callback != null) { + callback.run(); + decrementToken((Token) msg.obj); + } else { + super.handleMessage(msg); + } + } + }; + + private final Map TOKENS = new HashMap<>(); + + private MainThreadExecutor() { + // should not be instantiated + } + + /** + * Store a new task in the map for providing cancellation. This method is + * used by AndroidAnnotations and not intended to be called by clients. + * + * @param id the identifier of the task + * @param task the task itself + * @param delay the delay or zero to run immediately + */ + public void runTask(String id, Runnable task, long delay) { + if ("".equals(id)) { + HANDLER.postDelayed(task, delay); + return; + } + long time = SystemClock.uptimeMillis() + delay; + HANDLER.postAtTime(task, nextToken(id), time); + } + + private Token nextToken(String id) { + synchronized (TOKENS) { + Token token = TOKENS.get(id); + if (token == null) { + token = new MainThreadExecutor.Token(id); + TOKENS.put(id, token); + } + token.runnablesCount++; + return token; + } + } + + private void decrementToken(Token token) { + synchronized (TOKENS) { + if (--token.runnablesCount == 0) { + String id = token.id; + Token old = TOKENS.remove(id); + if (old != token) { + // a runnable finished after cancelling, we just removed a + // wrong token, lets put it back + TOKENS.put(id, old); + } + } + } + } + + /** + * Cancel all tasks having the specified id. + * + * @param id the cancellation identifier + */ + public void cancelAll(String id) { + Token token; + synchronized (TOKENS) { + token = TOKENS.remove(id); + } + if (token == null) { + // nothing to cancel + return; + } + HANDLER.removeCallbacksAndMessages(token); + } + + private final class Token { + int runnablesCount = 0; + final String id; + + private Token(String id) { + this.id = id; + } + } + + } + +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_thumb.xml b/app/src/main/res/drawable/circle_thumb.xml new file mode 100644 index 0000000..39d0f8e --- /dev/null +++ b/app/src/main/res/drawable/circle_thumb.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/error_logo.png b/app/src/main/res/drawable/error_logo.png new file mode 100644 index 0000000..46cff06 Binary files /dev/null and b/app/src/main/res/drawable/error_logo.png differ diff --git a/app/src/main/res/drawable/ic_baseline_close_24.xml b/app/src/main/res/drawable/ic_baseline_close_24.xml new file mode 100644 index 0000000..16d6d37 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_close_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_videocam_24a.xml b/app/src/main/res/drawable/ic_baseline_videocam_24a.xml new file mode 100644 index 0000000..340bff2 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_videocam_24a.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_white_pause.png b/app/src/main/res/drawable/ic_white_pause.png new file mode 100644 index 0000000..15e08b5 Binary files /dev/null and b/app/src/main/res/drawable/ic_white_pause.png differ diff --git a/app/src/main/res/drawable/ic_white_play.png b/app/src/main/res/drawable/ic_white_play.png new file mode 100644 index 0000000..33169d5 Binary files /dev/null and b/app/src/main/res/drawable/ic_white_play.png differ diff --git a/app/src/main/res/drawable/round_corner_white_fill3.xml b/app/src/main/res/drawable/round_corner_white_fill3.xml new file mode 100644 index 0000000..704ac04 --- /dev/null +++ b/app/src/main/res/drawable/round_corner_white_fill3.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/seekbar_drawable_video.xml b/app/src/main/res/drawable/seekbar_drawable_video.xml new file mode 100644 index 0000000..46ca651 --- /dev/null +++ b/app/src/main/res/drawable/seekbar_drawable_video.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/time_line_a.png b/app/src/main/res/drawable/time_line_a.png new file mode 100644 index 0000000..6050902 Binary files /dev/null and b/app/src/main/res/drawable/time_line_a.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..2853329 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_new_post.xml b/app/src/main/res/layout/activity_new_post.xml new file mode 100644 index 0000000..17584dc --- /dev/null +++ b/app/src/main/res/layout/activity_new_post.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_video_trim.xml b/app/src/main/res/layout/activity_video_trim.xml new file mode 100644 index 0000000..27bdba9 --- /dev/null +++ b/app/src/main/res/layout/activity_video_trim.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..703bd70 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,15 @@ + + + #6200EE + #3700B3 + #03DAC5 + + #65b7b7b7 + #0e1f2f + #000000 + #3be3e3 + #FF15FF00 + #00000000 + #ffffff + + \ No newline at end of file diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml new file mode 100644 index 0000000..a6dfba3 --- /dev/null +++ b/app/src/main/res/values/dimen.xml @@ -0,0 +1,14 @@ + + + + @dimen/_8sdp + @dimen/_5sdp + @dimen/_165sdp + @dimen/_28sdp + + @dimen/_19ssp + @dimen/_12sdp + + 62dp + 150dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..77e42c3 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,12 @@ + + VideoTrimmerApplication + Save + Cancel + Done + Select 1 min video + Video should be of minimum 3 seconds + Should allow permission to work app correct + Some Permissions were Denied + File format is not supported + com.demo.videotrimmer.provider + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..0fe5728 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/ahmedbadereldin/videotrimmerapplication/ExampleUnitTest.kt b/app/src/test/java/com/ahmedbadereldin/videotrimmerapplication/ExampleUnitTest.kt new file mode 100644 index 0000000..1eebf5f --- /dev/null +++ b/app/src/test/java/com/ahmedbadereldin/videotrimmerapplication/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.ahmedbadereldin.videotrimmerapplication + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..59b07ee --- /dev/null +++ b/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = "1.3.72" + repositories { + google() + jcenter() + } + dependencies { + classpath "com.android.tools.build:gradle:4.0.1" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + maven { url "https://www.jitpack.io" } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..4d15d01 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1ab4bdb --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Aug 09 08:23:17 EEST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6cb3ea2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name = "VideoTrimmerApplication" \ No newline at end of file