Skip to content

Commit 0e2605d

Browse files
implement photo picker
1 parent a7f8fc2 commit 0e2605d

22 files changed

+457
-57
lines changed

SSffmpegVideoOperation/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ android {
6868

6969
dependencies {
7070
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
71-
implementation 'androidx.core:core-ktx:1.8.0'
72-
implementation 'androidx.appcompat:appcompat:1.4.2'
73-
implementation 'com.google.android.material:material:1.6.1'
71+
implementation 'androidx.core:core-ktx:1.9.0'
72+
implementation 'androidx.appcompat:appcompat:1.7.1'
73+
implementation 'com.google.android.material:material:1.12.0'
7474
implementation 'pub.devrel:easypermissions:3.0.0'
7575
implementation(files("libs/mobile-ffmpeg.aar"))
7676
implementation 'com.github.jaiselrahman:FilePicker:1.3.2'

SSffmpegVideoOperation/src/main/java/com/simform/videooperations/Common.kt

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.simform.videooperations
22

3+
import android.content.ContentResolver
34
import android.content.Context
45
import android.content.Intent
56
import android.media.MediaExtractor
67
import android.media.MediaFormat
8+
import android.media.MediaMetadataRetriever
9+
import android.net.Uri
10+
import android.os.Build
11+
import android.os.Environment
12+
import android.provider.OpenableColumns
713
import android.text.TextUtils
14+
import androidx.activity.result.contract.ActivityResultContracts
815
import androidx.appcompat.app.AppCompatActivity
916
import com.jaiselrahman.filepicker.activity.FilePickerActivity
1017
import com.jaiselrahman.filepicker.config.Configurations
1118
import java.io.File
1219
import java.io.FileInputStream
20+
import java.io.FileOutputStream
1321
import java.io.IOException
1422
import java.text.DecimalFormat
1523
import java.util.Formatter
@@ -162,7 +170,20 @@ object Common {
162170
}
163171

164172
fun getFilePath(context: Context, fileExtension: String) : String {
165-
val dir = File(context.getExternalFilesDir(Common.OUT_PUT_DIR).toString())
173+
val dir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
174+
when {
175+
TextUtils.equals(fileExtension, VIDEO) -> Environment.DIRECTORY_MOVIES
176+
TextUtils.equals(fileExtension, IMAGE) || TextUtils.equals(fileExtension, GIF) -> Environment.DIRECTORY_PICTURES
177+
TextUtils.equals(fileExtension, MP3) -> Environment.DIRECTORY_MUSIC
178+
else -> Environment.DIRECTORY_DOWNLOADS
179+
}.let {
180+
File(Environment.getExternalStoragePublicDirectory(it).toString())
181+
}
182+
} else {
183+
// Fallback to app's private external files dir on older Android versions
184+
File(context.getExternalFilesDir(Common.OUT_PUT_DIR).toString())
185+
}
186+
166187
if (!dir.exists()) {
167188
dir.mkdirs()
168189
}
@@ -181,7 +202,12 @@ object Common {
181202
extension = ".mp3"
182203
}
183204
}
184-
val dest = File(dir.path + File.separator + Common.OUT_PUT_DIR + System.currentTimeMillis().div(1000L) + extension)
205+
val dest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
206+
File(dir, File.separator + System.currentTimeMillis().div(1000L) + extension)
207+
} else {
208+
// Fallback for devices below Android 14
209+
File(dir.path + File.separator + OUT_PUT_DIR + System.currentTimeMillis().div(1000L) + extension)
210+
}
185211
return dest.absolutePath
186212
}
187213

@@ -196,4 +222,47 @@ object Common {
196222
}
197223
}
198224
}
225+
226+
fun saveFileToTempAndGetPath(context: Context, uri: Uri): String? {
227+
val contentResolver = context.contentResolver
228+
val fileName = getFileNameFromUri(contentResolver, uri)
229+
val tempFile = File(context.cacheDir, fileName)
230+
231+
return try {
232+
contentResolver.openInputStream(uri)?.use { inputStream ->
233+
FileOutputStream(tempFile).use { outputStream ->
234+
inputStream.copyTo(outputStream)
235+
}
236+
}
237+
tempFile.absolutePath
238+
} catch (e: Exception) {
239+
e.printStackTrace()
240+
null
241+
}
242+
}
243+
244+
private fun getFileNameFromUri(contentResolver: ContentResolver, uri: Uri): String {
245+
var name = "temp_file"
246+
val returnCursor = contentResolver.query(uri, null, null, null, null)
247+
returnCursor?.use {
248+
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
249+
if (it.moveToFirst() && nameIndex >= 0) {
250+
name = it.getString(nameIndex)
251+
}
252+
}
253+
return name
254+
}
255+
256+
fun getDurationFromFile(file: File): Long {
257+
val retriever = MediaMetadataRetriever()
258+
return try {
259+
retriever.setDataSource(file.absolutePath)
260+
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
261+
} catch (e: Exception) {
262+
e.printStackTrace()
263+
0L
264+
} finally {
265+
retriever.release()
266+
}
267+
}
199268
}

app/build.gradle

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ kapt {
5050

5151
dependencies {
5252
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
53-
implementation 'androidx.core:core-ktx:1.8.0'
54-
implementation 'androidx.appcompat:appcompat:1.4.2'
55-
implementation 'com.github.bumptech.glide:glide:4.12.0'
56-
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
57-
implementation 'com.google.android.material:material:1.6.1'
58-
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
53+
implementation 'androidx.core:core-ktx:1.9.0'
54+
implementation 'androidx.appcompat:appcompat:1.7.1'
55+
implementation 'com.github.bumptech.glide:glide:4.16.0'
56+
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
57+
implementation 'com.google.android.material:material:1.12.0'
58+
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
5959
implementation 'pub.devrel:easypermissions:3.0.0'
6060
implementation 'com.github.jaiselrahman:FilePicker:1.3.2'
6161
implementation project(':SSffmpegVideoOperation')
62+
implementation "androidx.activity:activity-ktx:1.10.1"
63+
implementation "androidx.fragment:fragment-ktx:1.8.8"
6264
}

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
package="com.simform.videoimageeditor">
55

66
<uses-permission android:name="android.permission.CAMERA" />
7-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
7+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
8+
android:maxSdkVersion="32" />
89
<uses-permission
910
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
1011
tools:ignore="ScopedStorage" />

app/src/main/java/com/simform/videoimageeditor/BaseActivity.kt

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ package com.simform.videoimageeditor
22

33
import android.content.Intent
44
import android.media.MediaMetadataRetriever
5+
import android.os.Build
56
import android.os.Bundle
67
import android.view.MenuItem
78
import android.view.View
9+
import android.widget.Toast
10+
import androidx.activity.result.contract.ActivityResultContracts
811
import androidx.appcompat.app.AppCompatActivity
912
import com.jaiselrahman.filepicker.activity.FilePickerActivity
1013
import com.jaiselrahman.filepicker.model.MediaFile
14+
import com.simform.videooperations.Common
1115
import com.simform.videooperations.FFmpegQueryExtension
1216
import com.simform.videooperations.FileSelection
1317

@@ -24,6 +28,68 @@ abstract class BaseActivity(view: Int, title: Int) : AppCompatActivity(), View.O
2428
var retriever: MediaMetadataRetriever? = null
2529
val utils = Utils()
2630
val ffmpegQueryExtension = FFmpegQueryExtension()
31+
val pickSingleMedia =
32+
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
33+
if (uri != null) {
34+
val mediaType = when (contentResolver.getType(uri)?.substringBefore('/')) {
35+
"image" -> MediaFile.TYPE_IMAGE
36+
"video" -> MediaFile.TYPE_VIDEO
37+
"audio" -> MediaFile.TYPE_AUDIO
38+
else -> MediaFile.TYPE_FILE
39+
}
40+
41+
val mediaFile = MediaFile().apply {
42+
setUri(uri)
43+
setMediaType(mediaType)
44+
setMimeType(contentResolver.getType(uri))
45+
setName(uri.lastPathSegment ?: "Unknown")
46+
}
47+
48+
val requestCode = when (mediaType) {
49+
MediaFile.TYPE_IMAGE -> Common.IMAGE_FILE_REQUEST_CODE
50+
MediaFile.TYPE_VIDEO -> Common.VIDEO_FILE_REQUEST_CODE
51+
MediaFile.TYPE_AUDIO -> Common.AUDIO_FILE_REQUEST_CODE
52+
else -> Common.VIDEO_FILE_REQUEST_CODE // Default to video for unknown types
53+
}
54+
55+
val mediaFiles = listOf(mediaFile)
56+
this.mediaFiles = mediaFiles
57+
(this as FileSelection).selectedFiles(mediaFiles, requestCode)
58+
} else {
59+
Toast.makeText(this, "User cancelled the selection", Toast.LENGTH_SHORT).show()
60+
}
61+
}
62+
63+
val pickMultipleMedia = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) { uris ->
64+
if (uris.isNotEmpty()) {
65+
val mediaFiles = uris.map { uri ->
66+
val mediaType = when (contentResolver.getType(uri)?.substringBefore('/')) {
67+
"image" -> MediaFile.TYPE_IMAGE
68+
"video" -> MediaFile.TYPE_VIDEO
69+
"audio" -> MediaFile.TYPE_AUDIO
70+
else -> MediaFile.TYPE_FILE
71+
}
72+
73+
MediaFile().apply {
74+
setUri(uri)
75+
setMediaType(mediaType)
76+
setMimeType(contentResolver.getType(uri))
77+
setName(uri.lastPathSegment ?: "Unknown")
78+
}
79+
}
80+
val requestCode = when (contentResolver.getType(mediaFiles.first().uri)?.substringBefore('/')) {
81+
"image" -> Common.IMAGE_FILE_REQUEST_CODE
82+
"video" -> Common.VIDEO_FILE_REQUEST_CODE
83+
"audio" -> Common.AUDIO_FILE_REQUEST_CODE
84+
else -> Common.VIDEO_FILE_REQUEST_CODE// Default to video for unknown types
85+
}
86+
this.mediaFiles = mediaFiles
87+
(this as FileSelection).selectedFiles(mediaFiles, requestCode)
88+
} else {
89+
Toast.makeText(this, "User cancelled the selection", Toast.LENGTH_SHORT).show()
90+
}
91+
92+
}
2793

2894
override fun onCreate(savedInstanceState: Bundle?) {
2995
super.onCreate(savedInstanceState)
@@ -43,7 +109,7 @@ abstract class BaseActivity(view: Int, title: Int) : AppCompatActivity(), View.O
43109

44110
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
45111
super.onActivityResult(requestCode, resultCode, data)
46-
if (resultCode == RESULT_OK && data != null) {
112+
if (resultCode == RESULT_OK && data != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
47113
mediaFiles = data.getParcelableArrayListExtra(FilePickerActivity.MEDIA_FILES)
48114
(this as FileSelection).selectedFiles(mediaFiles,requestCode)
49115
}

app/src/main/java/com/simform/videoimageeditor/otherFFMPEGProcessActivity/MergeGIFActivity.kt

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.simform.videoimageeditor.otherFFMPEGProcessActivity
22

33
import android.annotation.SuppressLint
4+
import android.os.Build
45
import android.text.TextUtils
56
import android.view.View
67
import android.widget.ImageView
@@ -21,6 +22,8 @@ import com.simform.videooperations.FFmpegCallBack
2122
import com.simform.videooperations.LogMessage
2223
import com.simform.videooperations.Paths
2324
import java.io.File
25+
import androidx.activity.result.PickVisualMediaRequest
26+
import androidx.activity.result.contract.ActivityResultContracts
2427

2528

2629
class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merge_gif) {
@@ -38,7 +41,16 @@ class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merg
3841
override fun onClick(v: View?) {
3942
when (v?.id) {
4043
R.id.btnGifPath -> {
41-
Common.selectFile(this, maxSelection = 2, isImageSelection = true, isAudioSelection = false)
44+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
45+
pickMultipleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
46+
} else {
47+
Common.selectFile(
48+
this,
49+
maxSelection = 2,
50+
isImageSelection = true,
51+
isAudioSelection = false
52+
)
53+
}
4254
}
4355
R.id.btnMerge -> {
4456
when {
@@ -84,7 +96,12 @@ class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merg
8496
mediaFiles?.let {
8597
for (element in it) {
8698
val paths = Paths()
87-
paths.filePath = element.path
99+
paths.filePath =
100+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
101+
Common.saveFileToTempAndGetPath(this, element.uri) ?: ""
102+
} else {
103+
element.path
104+
}
88105
paths.isImageFile = true
89106
pathsList.add(paths)
90107
}
@@ -145,8 +162,15 @@ class MergeGIFActivity : BaseActivity(R.layout.activity_merge_gif, R.string.merg
145162
val size: Int = mediaFiles.size
146163
var isGifFile = true
147164
for (i in 0 until size) {
148-
if (File(mediaFiles[i].path).extension != "gif") {
149-
isGifFile = false
165+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
166+
if (mediaFiles[i].mimeType != "image/gif") {
167+
isGifFile = false
168+
}
169+
} else {
170+
// For older versions, check the file extension
171+
if (File(mediaFiles[i].path).extension != "gif") {
172+
isGifFile = false
173+
}
150174
}
151175
}
152176
if (size == 2 && isGifFile) {

app/src/main/java/com/simform/videoimageeditor/videoProcessActivity/AddTextOnVideoActivity.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package com.simform.videoimageeditor.videoProcessActivity
22

33
import android.annotation.SuppressLint
44
import android.media.MediaMetadataRetriever
5+
import android.os.Build
56
import android.text.TextUtils
67
import android.view.View
78
import android.widget.Toast
9+
import androidx.activity.result.PickVisualMediaRequest
10+
import androidx.activity.result.contract.ActivityResultContracts
811
import com.jaiselrahman.filepicker.model.MediaFile
912
import com.simform.videoimageeditor.BaseActivity
1013
import com.simform.videoimageeditor.R
@@ -13,7 +16,6 @@ import com.simform.videooperations.CallBackOfQuery
1316
import com.simform.videooperations.Common
1417
import com.simform.videooperations.Common.getFileFromAssets
1518
import com.simform.videooperations.FFmpegCallBack
16-
import com.simform.videooperations.FFmpegQueryExtension
1719
import com.simform.videooperations.LogMessage
1820
import java.util.concurrent.CompletableFuture.runAsync
1921

@@ -32,7 +34,12 @@ class AddTextOnVideoActivity : BaseActivity(R.layout.activity_add_text_on_video,
3234
override fun onClick(v: View?) {
3335
when (v?.id) {
3436
R.id.btnVideoPath -> {
35-
Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false)
37+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
38+
pickSingleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly))
39+
} else {
40+
// Fallback for devices below Android 13
41+
Common.selectFile(this, maxSelection = 1, isImageSelection = false, isAudioSelection = false)
42+
}
3643
}
3744
R.id.btnAdd -> {
3845
when {
@@ -114,7 +121,11 @@ class AddTextOnVideoActivity : BaseActivity(R.layout.activity_add_text_on_video,
114121
when (requestCode) {
115122
Common.VIDEO_FILE_REQUEST_CODE -> {
116123
if (mediaFiles != null && mediaFiles.isNotEmpty()) {
117-
binding.tvInputPathVideo.text = mediaFiles[0].path
124+
binding.tvInputPathVideo.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
125+
Common.saveFileToTempAndGetPath(this, mediaFiles[0].uri)
126+
} else {
127+
mediaFiles[0].path
128+
}
118129
isInputVideoSelected = true
119130
runAsync {
120131
retriever = MediaMetadataRetriever()

0 commit comments

Comments
 (0)