diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt
index 38380fb7..b3a2d35a 100644
--- a/app/src/main/java/com/futo/platformplayer/Settings.kt
+++ b/app/src/main/java/com/futo/platformplayer/Settings.kt
@@ -534,6 +534,13 @@ class Settings : FragmentedStorageFileJson() {
StateApp.instance.changeExternalDownloadDirectory(it);
}
}
+
+ @FormField(R.string.clear_external_downloads_directory, FieldForm.BUTTON, R.string.clear_the_external_storage_for_download_files, 5)
+ fun clearStorageDownload() {
+ Settings.instance.storage.storage_download = null;
+ Settings.instance.save();
+ SettingsActivity.getActivity()?.let { UIDialogs.toast(it, "Cleared download storage directory") };
+ }
}
diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt
index 3d93437c..0e57a220 100644
--- a/app/src/main/java/com/futo/platformplayer/Utility.kt
+++ b/app/src/main/java/com/futo/platformplayer/Utility.kt
@@ -164,9 +164,7 @@ fun Int.sp(resources: Resources): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), resources.displayMetrics).toInt()
}
-fun File.share(context: Context) {
- val uri = FileProvider.getUriForFile(context, context.resources.getString(R.string.authority), this);
-
+fun DocumentFile.share(context: Context) {
val shareIntent = Intent();
shareIntent.action = Intent.ACTION_SEND;
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt
index 987dd8c6..37f1395f 100644
--- a/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt
+++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt
@@ -1,13 +1,18 @@
package com.futo.platformplayer.downloads
+import android.content.Context
+import android.net.Uri
import android.os.Environment
+import androidx.documentfile.provider.DocumentFile
import com.arthenica.ffmpegkit.*
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.logging.Logger
+import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.toHumanBitrate
import kotlinx.coroutines.*
import java.io.*
+import java.util.UUID
import java.util.concurrent.CancellationException
import java.util.concurrent.Executors
import kotlin.coroutines.resumeWithException
@@ -43,7 +48,7 @@ class VideoExport {
this.subtitleSource = subtitleSource;
}
- suspend fun export(onProgress: ((Double) -> Unit)? = null): File = coroutineScope {
+ suspend fun export(context: Context, onProgress: ((Double) -> Unit)? = null): DocumentFile = coroutineScope {
if(isCancelled) throw CancellationException("Export got cancelled");
val v = videoSource;
@@ -55,34 +60,47 @@ class VideoExport {
if (a != null) sourceCount++;
if (s != null) sourceCount++;
- var outputFile: File? = null;
- val moviesRoot = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
- val musicRoot = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
- val moviesGrayjay = File(moviesRoot, "Grayjay");
- val musicGrayjay = File(musicRoot, "Grayjay");
- if(!moviesGrayjay.exists())
- moviesGrayjay.mkdirs();
- if(!musicGrayjay.exists())
- musicGrayjay.mkdirs();
-
+ val outputFile: DocumentFile?;
+ val downloadRoot = StateApp.instance.getExternalDownloadDirectory(context) ?: throw Exception("External download directory is not set");
if (sourceCount > 1) {
val outputFileName = toSafeFileName(videoLocal.name) + ".mp4"// + VideoDownload.videoContainerToExtension(v.container);
- val f = File(moviesGrayjay, outputFileName);
+ val f = downloadRoot.createFile("video/mp4", outputFileName)
+ ?: throw Exception("Failed to create file in external directory.");
Logger.i(TAG, "Combining video and audio through FFMPEG.");
- combine(a?.filePath, v?.filePath, s?.filePath, f.absolutePath, videoLocal.duration.toDouble()) { progress -> onProgress?.invoke(progress) };
+ val tempFile = File(context.cacheDir, "${UUID.randomUUID()}.mp4");
+ try {
+ combine(a?.filePath, v?.filePath, s?.filePath, tempFile.absolutePath, videoLocal.duration.toDouble()) { progress -> onProgress?.invoke(progress) };
+ context.contentResolver.openOutputStream(f.uri)?.use { outputStream ->
+ copy(tempFile.absolutePath, outputStream) { progress -> onProgress?.invoke(progress) };
+ }
+ } finally {
+ tempFile.delete();
+ }
outputFile = f;
} else if (v != null) {
val outputFileName = toSafeFileName(videoLocal.name) + "." + VideoDownload.videoContainerToExtension(v.container);
- val f = File(moviesGrayjay, outputFileName);
+ val f = downloadRoot.createFile(v.container, outputFileName)
+ ?: throw Exception("Failed to create file in external directory.");
+
Logger.i(TAG, "Copying video.");
- copy(v.filePath, f.absolutePath) { progress -> onProgress?.invoke(progress) };
+
+ context.contentResolver.openOutputStream(f.uri)?.use { outputStream ->
+ copy(v.filePath, outputStream) { progress -> onProgress?.invoke(progress) };
+ }
+
outputFile = f;
} else if (a != null) {
val outputFileName = toSafeFileName(videoLocal.name) + "." + VideoDownload.audioContainerToExtension(a.container);
- val f = File(musicGrayjay, outputFileName);
+ val f = downloadRoot.createFile(a.container, outputFileName)
+ ?: throw Exception("Failed to create file in external directory.");
+
Logger.i(TAG, "Copying audio.");
- copy(a.filePath, f.absolutePath) { progress -> onProgress?.invoke(progress) };
+
+ context.contentResolver.openOutputStream(f.uri)?.use { outputStream ->
+ copy(a.filePath, outputStream) { progress -> onProgress?.invoke(progress) };
+ }
+
outputFile = f;
} else {
throw Exception("Cannot export when no audio or video source is set.");
@@ -179,10 +197,9 @@ class VideoExport {
}
}
- private suspend fun copy(fromPath: String, toPath: String, bufferSize: Int = 8192, onProgress: ((Double) -> Unit)? = null) {
+ private suspend fun copy(fromPath: String, outputStream: OutputStream, bufferSize: Int = 8192, onProgress: ((Double) -> Unit)? = null) {
withContext(Dispatchers.IO) {
var inputStream: FileInputStream? = null
- var outputStream: FileOutputStream? = null
try {
val srcFile = File(fromPath)
@@ -190,17 +207,7 @@ class VideoExport {
throw IOException("Source file not found.")
}
- val dstFile = File(toPath)
- val parentDir = dstFile.parentFile ?: throw IOException("Non existent parent dir.")
-
- if (!parentDir.exists()) {
- if (!parentDir.mkdirs()) {
- throw IOException("Failed to create destination directory.")
- }
- }
-
inputStream = FileInputStream(srcFile)
- outputStream = FileOutputStream(dstFile)
val buffer = ByteArray(bufferSize)
val totalBytes = srcFile.length()
@@ -221,7 +228,6 @@ class VideoExport {
throw IOException("Error occurred while copying file: ${e.message}", e)
} finally {
inputStream?.close()
- outputStream?.close()
}
}
}
diff --git a/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt b/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt
index 7593c8b0..659d222a 100644
--- a/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt
+++ b/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt
@@ -107,7 +107,7 @@ class ExportingService : Service() {
{
try{
notifyExport(currentExport);
- doExport(currentExport);
+ doExport(applicationContext, currentExport);
}
catch(ex: Throwable) {
Logger.e(TAG, "Failed export [${currentExport.videoLocal.name}]: ${ex.message}", ex);
@@ -125,13 +125,13 @@ class ExportingService : Service() {
stopService(this);
}
- private suspend fun doExport(export: VideoExport) {
+ private suspend fun doExport(context: Context, export: VideoExport) {
Logger.i(TAG, "Exporting [${export.videoLocal.name}] started");
export.changeState(VideoExport.State.EXPORTING);
var lastNotifyTime: Long = 0L;
- val file = export.export { progress ->
+ val file = export.export(context) { progress ->
export.progress = progress;
val currentTime = System.currentTimeMillis();
@@ -146,7 +146,7 @@ class ExportingService : Service() {
notifyExport(export);
withContext(Dispatchers.Main) {
- StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), "File exported", "Exported [${file.path}]", AnnouncementType.SESSION, time = null, category = "download", actionButton = "Open") {
+ StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), "File exported", "Exported [${file.uri}]", AnnouncementType.SESSION, time = null, category = "download", actionButton = "Open") {
file.share(this@ExportingService);
};
}
diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt
index e6d34609..c30a4311 100644
--- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt
+++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt
@@ -111,17 +111,13 @@ class StateApp {
return null;
}
fun changeExternalDownloadDirectory(context: IWithResultLauncher, onChanged: ((DocumentFile?)->Unit)? = null) {
-
- scopeOrNull?.launch(Dispatchers.Main) {
- UIDialogs.toast("External download directory not yet used by export (WIP)");
- };
if(context is Context)
requestDirectoryAccess(context, "Download Exports", "This directory is used to export downloads to for external usage.", null) {
if(it != null)
context.contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_WRITE_URI_PERMISSION.or(Intent.FLAG_GRANT_READ_URI_PERMISSION));
if(it != null && isValidStorageUri(context, it)) {
Logger.i(TAG, "Changed external download directory: ${it}");
- Settings.instance.storage.storage_general = it.toString();
+ Settings.instance.storage.storage_download = it.toString();
Settings.instance.save();
onChanged?.invoke(getExternalDownloadDirectory(context));
diff --git a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt
index fa017213..f6cbd9b7 100644
--- a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt
+++ b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt
@@ -400,10 +400,7 @@ class StateDownloads {
_exporting.save(videoExport);
if(notify) {
- if(videoSource == null)
- UIDialogs.toast("Exporting [${shortName}]\nIn your music directory under Grayjay");
- else
- UIDialogs.toast("Exporting [${shortName}]\nIn your movies directory under Grayjay");
+ UIDialogs.toast("Exporting [${shortName}]");
StateApp.withContext { ExportingService.getOrCreateService(it) };
onExportsChanged.emit();
}
diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt
index 3fd9a3cd..a8d58d5b 100644
--- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt
+++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt
@@ -1,5 +1,6 @@
package com.futo.platformplayer.views.adapters.viewholders
+import android.app.Activity
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageButton
@@ -7,9 +8,11 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.futo.platformplayer.*
+import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.downloads.VideoLocal
import com.futo.platformplayer.images.GlideHelper.Companion.loadThumbnails
+import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.views.adapters.AnyAdapter
@@ -47,7 +50,18 @@ class VideoDownloadViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder<
}
_videoExport.setOnClickListener {
val v = _video ?: return@setOnClickListener;
- StateDownloads.instance.export(v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull());
+ if (StateApp.instance.getExternalDownloadDirectory(_view.context) == null) {
+ StateApp.instance.changeExternalDownloadDirectory(_view.context as MainActivity) {
+ if (it == null) {
+ UIDialogs.toast(_view.context, "Download directory must be set to export.");
+ return@changeExternalDownloadDirectory;
+ }
+
+ StateDownloads.instance.export(v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull());
+ };
+ } else {
+ StateDownloads.instance.export(v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull());
+ }
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7aa634f2..f55cf32e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -278,9 +278,11 @@
Casting
Change behavior of the player
Change external Downloads directory
+ Clear external Downloads directory
Change external General directory
Change tabs visible on the home screen
Change the external directory for general files
+ Clear the external storage for download files
Change the external storage for download files
Clear Cookies
Clear Cookies on Logout