Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 168 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,27 +62,130 @@ FFmpeg Android runs on the following architectures:
### Dependency
- [MobileFFmpeg](https://github.com/tanersener/mobile-ffmpeg)

### Gradle Dependency
* Add it in your root build.gradle at the end of repositories:
**Note:** This library includes the mobile-ffmpeg.aar file (full version) in the `SSffmpegVideoOperation/libs/` directory. The full version includes all FFmpeg features and codecs needed for comprehensive video operations.

```
**For JitPack users:** The mobile-ffmpeg dependency is automatically included when you use the JitPack dependency. No additional setup required!

### Integration Guide

This library uses the mobile-ffmpeg AAR dependency which is included in the library's libs folder. Follow these simple steps to integrate the library into your project.

#### Step 1: Download the Library
* Download or clone this repository
* Copy the `SSffmpegVideoOperation` module folder to your project

#### Step 2: Integration Methods

**Option A: Using JitPack (Recommended - Simple One-Line Integration)**

This is the easiest way to integrate the library. JitPack will build the library along with the mobile-ffmpeg dependency automatically.

* Add JitPack repository to your root build.gradle:

```gradle
allprojects {
repositories {
...
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
```

* Add the dependency in your app's build.gradle file
* Add the dependency in your app's build.gradle file:

```
```gradle
dependencies {
implementation 'com.github.SimformSolutionsPvtLtd:SSffmpegVideoOperation:1.0.8'
}
```

This is all you have to do to load the FFmpeg library.
**That's it!** JitPack will automatically handle the mobile-ffmpeg.aar dependency that's included in this repository. No additional configuration needed.

**Option B: Local Integration (For Customization)**

If you want to modify the library or integrate it locally, follow these steps:

1. **Add the library module to your `settings.gradle`:**
```gradle
include ':app', ':SSffmpegVideoOperation'
// If the SSffmpegVideoOperation folder is in a different location:
// project(':SSffmpegVideoOperation').projectDir = new File('path/to/SSffmpegVideoOperation')
```

2. **Configure repositories in your app's `build.gradle`:**
```gradle
android {
// ... your existing configuration
}

repositories {
google()
mavenCentral()
flatDir {
dirs '../SSffmpegVideoOperation/libs'
}
}

dependencies {
implementation project(':SSffmpegVideoOperation')
// ... your other dependencies
}
```

3. **The SSffmpegVideoOperation module is already configured with:**
- `libs/mobile-ffmpeg.aar` - The full version of mobile-ffmpeg
- Proper repository configuration in its `build.gradle`:
```gradle
repositories {
flatDir {
dirs 'libs'
}
}

dependencies {
implementation(name: 'mobile-ffmpeg', ext: 'aar')
// ... other dependencies
}
```

**Important Notes:**
- The `mobile-ffmpeg.aar` file is already included in `SSffmpegVideoOperation/libs/`
- No additional setup is required for the AAR dependency
- The library uses `flatDir` repository to resolve the local AAR file
- Make sure to sync your project after adding the module

## How JitPack Integration Works

When you use the JitPack dependency (`implementation 'com.github.SimformSolutionsPvtLtd:SSffmpegVideoOperation:1.0.8'`):

1. **JitPack automatically builds** your library from the GitHub repository
2. **Includes mobile-ffmpeg.aar** - The AAR file in `SSffmpegVideoOperation/libs/` is packaged with the library
3. **Resolves dependencies** - All transitive dependencies are handled automatically
4. **No local setup needed** - Users don't need to manually handle AAR files or repository configurations

This approach gives you the best of both worlds:
- **Simple integration** for users via JitPack
- **Full control** over the mobile-ffmpeg dependency
- **No external dependencies** on repositories that might change or become unavailable

#### Step 3: Add Required Permissions
Add these permissions to your AndroidManifest.xml:
```xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```

#### Step 4: ProGuard Configuration (if using ProGuard/R8)
Add these rules to your proguard-rules.pro:
```
-keep class com.arthenica.mobileffmpeg.** { *; }
-keep class com.simform.videooperations.** { *; }
```

This setup ensures proper AAR dependency resolution and avoids the "Direct local .aar file dependencies are not supported" error when building AAR libraries.

### Run FFmpeg command
In this sample code we will run the FFmpeg -version command in background call.
Expand Down Expand Up @@ -138,6 +241,64 @@ CallBackOfQuery().callQuery(query, object : FFmpegCallBack {
same for other queries.
And you can apply your query also

## Troubleshooting

### Common Issues and Solutions

#### 1. "Could not find mobile-ffmpeg.aar" Error
**Solution:**
- Ensure the `SSffmpegVideoOperation/libs/mobile-ffmpeg.aar` file exists
- Check that your app's build.gradle has the correct flatDir repository configuration:
```gradle
repositories {
flatDir {
dirs '../SSffmpegVideoOperation/libs'
}
}
```

#### 2. "INSTALL_PARSE_FAILED_NO_CERTIFICATES" Error
**Solution:** Ensure your release builds are properly signed. Add signing configuration to your app's build.gradle:
```gradle
android {
signingConfigs {
debug {
storeFile file("${System.getProperty('user.home')}/.android/debug.keystore")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
buildTypes {
release {
signingConfig signingConfigs.debug // Use debug signing for testing
// For production, create and use a proper release keystore
}
}
}
```

#### 3. Build Sync Issues
**Solution:**
- Make sure the SSffmpegVideoOperation module is properly included in settings.gradle
- Verify the module path is correct
- Clean and rebuild the project

#### 4. FFmpeg Commands Not Working
**Solution:**
- Check if you have the required permissions
- Ensure input and output file paths are correct and accessible
- Verify the FFmpeg command syntax

#### 5. Memory Issues with Large Videos
**Solution:**
- Process videos in smaller chunks
- Use appropriate compression settings
- Consider using background processing for large files

#### 6. "flatDir should be avoided" Warning
**Solution:** This warning can be ignored. While flatDir is not the recommended approach for published libraries, it's acceptable for local AAR dependencies and works reliably for this use case.

## Medium Blog
For more info go to __[Multimedia Operations for Android using FFmpeg](https://medium.com/simform-engineering/multimedia-operations-for-android-using-ffmpeg-78f1fb480a83)__

Expand Down
1 change: 1 addition & 0 deletions SSffmpegVideoOperation/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/captures
.externalNativeBuild
libs/
!libs/mobile-ffmpeg.aar
.idea/gradle.xml
.idea/misc.xml
.idea/modules.xml
Expand Down
18 changes: 9 additions & 9 deletions SSffmpegVideoOperation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,19 @@ afterEvaluate {
}

repositories {
google()
mavenCentral()
flatDir {
dirs 'libs'
}
}

android {
compileSdkVersion 30
buildToolsVersion "29.0.3"

namespace 'com.simform.videooperations'
defaultConfig {
compileSdk 35
minSdkVersion 24
targetSdkVersion 30
targetSdkVersion 35

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
Expand Down Expand Up @@ -69,11 +70,10 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'pub.devrel:easypermissions:3.0.0'
implementation(files("libs/mobile-ffmpeg.aar"))
implementation (name: 'mobile-ffmpeg', ext: 'aar')
implementation 'com.github.jaiselrahman:FilePicker:1.3.2'
}

Binary file added SSffmpegVideoOperation/libs/mobile-ffmpeg.aar
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.simform.videooperations

import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.media.MediaExtractor
import android.media.MediaFormat
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.OpenableColumns
import android.text.TextUtils
import androidx.appcompat.app.AppCompatActivity
import com.jaiselrahman.filepicker.activity.FilePickerActivity
import com.jaiselrahman.filepicker.config.Configurations
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.text.DecimalFormat
import java.util.Formatter
Expand Down Expand Up @@ -162,7 +169,20 @@ object Common {
}

fun getFilePath(context: Context, fileExtension: String) : String {
val dir = File(context.getExternalFilesDir(Common.OUT_PUT_DIR).toString())
val dir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
when {
TextUtils.equals(fileExtension, VIDEO) -> Environment.DIRECTORY_MOVIES
TextUtils.equals(fileExtension, IMAGE) || TextUtils.equals(fileExtension, GIF) -> Environment.DIRECTORY_PICTURES
TextUtils.equals(fileExtension, MP3) -> Environment.DIRECTORY_MUSIC
else -> Environment.DIRECTORY_DOWNLOADS
}.let {
File(Environment.getExternalStoragePublicDirectory(it).toString())
}
} else {
// Fallback to app's private external files dir on older Android versions
File(context.getExternalFilesDir(Common.OUT_PUT_DIR).toString())
}

if (!dir.exists()) {
dir.mkdirs()
}
Expand All @@ -181,7 +201,12 @@ object Common {
extension = ".mp3"
}
}
val dest = File(dir.path + File.separator + Common.OUT_PUT_DIR + System.currentTimeMillis().div(1000L) + extension)
val dest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
File(dir, System.currentTimeMillis().div(1000L).toString() + extension)
} else {
// Fallback for devices below Android 14
File(dir.path + File.separator + OUT_PUT_DIR + System.currentTimeMillis().div(1000L) + extension)
}
return dest.absolutePath
}

Expand All @@ -196,4 +221,47 @@ object Common {
}
}
}

fun saveFileToTempAndGetPath(context: Context, uri: Uri): String? {
val contentResolver = context.contentResolver
val fileName = getFileNameFromUri(contentResolver, uri)
val tempFile = File(context.cacheDir, fileName)

return try {
contentResolver.openInputStream(uri)?.use { inputStream ->
FileOutputStream(tempFile).use { outputStream ->
inputStream.copyTo(outputStream)
}
}
tempFile.absolutePath
} catch (e: Exception) {
e.printStackTrace()
null
}
}

private fun getFileNameFromUri(contentResolver: ContentResolver, uri: Uri): String {
var name = "temp_file"
val returnCursor = contentResolver.query(uri, null, null, null, null)
returnCursor?.use {
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (it.moveToFirst() && nameIndex >= 0) {
name = it.getString(nameIndex)
}
}
return name
}

fun getDurationFromFile(file: File): Long {
val retriever = MediaMetadataRetriever()
return try {
retriever.setDataSource(file.absolutePath)
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
} catch (e: Exception) {
e.printStackTrace()
0L
} finally {
retriever.release()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class FFmpegQueryExtension {
add("-i")
add(input)
add("-s")
add("${width}x${height}")
add("${((width ?: 0)/2) * 2}x${((height ?: 0)/2) * 2}") // Ensure width and height are even numbers
add("-vf")
add("format=yuv420p,$fade")
add("-t")
Expand Down
Loading