mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
@@ -4,6 +4,7 @@ import android.app.Dialog
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.SystemClock
|
||||||
import com.futo.platformplayer.UIDialogs.ActionStyle
|
import com.futo.platformplayer.UIDialogs.ActionStyle
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
@@ -23,6 +24,7 @@ class UpdateDownloadService : Service() {
|
|||||||
private const val MAX_RETRIES = 5
|
private const val MAX_RETRIES = 5
|
||||||
private const val INITIAL_BACKOFF_MS = 5_000L
|
private const val INITIAL_BACKOFF_MS = 5_000L
|
||||||
private const val BUFFER_SIZE = 8 * 1024
|
private const val BUFFER_SIZE = 8 * 1024
|
||||||
|
private const val MIN_PROGRESS_UPDATE_INTERVAL_MS = 500L
|
||||||
|
|
||||||
var updateDownloadedDialog: Dialog? = null
|
var updateDownloadedDialog: Dialog? = null
|
||||||
}
|
}
|
||||||
@@ -36,6 +38,8 @@ class UpdateDownloadService : Service() {
|
|||||||
@Volatile
|
@Volatile
|
||||||
private var cancelRequested: Boolean = false
|
private var cancelRequested: Boolean = false
|
||||||
|
|
||||||
|
private var lastProgressUpdateElapsedMs: Long = 0L
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder? = null
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
@@ -81,6 +85,16 @@ class UpdateDownloadService : Service() {
|
|||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun throttledUpdateDownloadProgress(version: Int, progress: Int, indeterminate: Boolean) {
|
||||||
|
val now = SystemClock.elapsedRealtime()
|
||||||
|
val force = progress == 100 && !indeterminate
|
||||||
|
|
||||||
|
if (force || now - lastProgressUpdateElapsedMs >= MIN_PROGRESS_UPDATE_INTERVAL_MS) {
|
||||||
|
lastProgressUpdateElapsedMs = now
|
||||||
|
UpdateNotificationManager.updateDownloadProgress(this, version, progress, indeterminate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun downloadApk(version: Int) {
|
private suspend fun downloadApk(version: Int) {
|
||||||
val apkFile = StateUpdate.getApkFile(this, version)
|
val apkFile = StateUpdate.getApkFile(this, version)
|
||||||
val partialFile = StateUpdate.getPartialApkFile(this, version)
|
val partialFile = StateUpdate.getPartialApkFile(this, version)
|
||||||
@@ -190,12 +204,18 @@ class UpdateDownloadService : Service() {
|
|||||||
progress > 100 -> 100
|
progress > 100 -> 100
|
||||||
else -> progress
|
else -> progress
|
||||||
}
|
}
|
||||||
UpdateNotificationManager.updateDownloadProgress(this, version, safeProgress, false)
|
throttledUpdateDownloadProgress(version, safeProgress, indeterminate = false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
UpdateNotificationManager.updateDownloadProgress(this, version, 0, true)
|
throttledUpdateDownloadProgress(version, progress = 0, indeterminate = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cancelRequested && totalBytes > 0L) {
|
||||||
|
val finalProgress = 100
|
||||||
|
throttledUpdateDownloadProgress(version, finalProgress, indeterminate = false)
|
||||||
|
}
|
||||||
|
|
||||||
output.flush()
|
output.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,12 +243,12 @@ class UpdateDownloadService : Service() {
|
|||||||
updateDownloadedDialog = UIDialogs.showDialog(ctx, R.drawable.foreground,
|
updateDownloadedDialog = UIDialogs.showDialog(ctx, R.drawable.foreground,
|
||||||
"Update downloaded",
|
"Update downloaded",
|
||||||
"Would you like to install it now?", null, 0,
|
"Would you like to install it now?", null, 0,
|
||||||
UIDialogs.Action("Cancel", {
|
UIDialogs.Action("Not now", {
|
||||||
updateDownloadedDialog = null
|
updateDownloadedDialog = null
|
||||||
}, ActionStyle.NONE, true),
|
}, ActionStyle.NONE, true),
|
||||||
UIDialogs.Action("Install", {
|
UIDialogs.Action("Install", {
|
||||||
UpdateNotificationManager.cancelAll(ctx)
|
UpdateNotificationManager.cancelAll(ctx)
|
||||||
UpdateInstaller.startInstall(ctx, apkFile)
|
UpdateInstaller.startInstall(ctx, version, apkFile)
|
||||||
}, ActionStyle.PRIMARY, true));
|
}, ActionStyle.PRIMARY, true));
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Logger.w(TAG, "Failed to show in-app update downloaded dialog", t)
|
Logger.w(TAG, "Failed to show in-app update downloaded dialog", t)
|
||||||
|
|||||||
@@ -26,15 +26,17 @@ object UpdateInstaller {
|
|||||||
private const val TAG = "UpdateInstaller"
|
private const val TAG = "UpdateInstaller"
|
||||||
|
|
||||||
@SuppressLint("RequestInstallPackagesPolicy")
|
@SuppressLint("RequestInstallPackagesPolicy")
|
||||||
fun startInstall(context: Context, apkFile: File) {
|
fun startInstall(context: Context, version: Int, apkFile: File) {
|
||||||
if (!apkFile.exists()) {
|
if (!apkFile.exists()) {
|
||||||
Logger.w(TAG, "APK file does not exist: ${apkFile.absolutePath}")
|
Logger.w(TAG, "APK file does not exist: ${apkFile.absolutePath}")
|
||||||
UIDialogs.toast(context, "Update file missing")
|
UIDialogs.toast(context, "Update file missing")
|
||||||
|
UpdateNotificationManager.showInstallFailedNotification(context, version, apkFile, "APK file does not exist.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BuildConfig.IS_PLAYSTORE_BUILD) {
|
if (BuildConfig.IS_PLAYSTORE_BUILD) {
|
||||||
UIDialogs.toast(context, "Updates are managed by the Play Store")
|
UIDialogs.toast(context, "Updates are managed by the Play Store")
|
||||||
|
UpdateNotificationManager.showInstallFailedNotification(context, version, apkFile, "Updates are managed by the Play Store.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ object UpdateInstaller {
|
|||||||
val pm = context.packageManager
|
val pm = context.packageManager
|
||||||
if (!pm.canRequestPackageInstalls()) {
|
if (!pm.canRequestPackageInstalls()) {
|
||||||
UIDialogs.toast(context, "Allow this app to install updates, then try again")
|
UIDialogs.toast(context, "Allow this app to install updates, then try again")
|
||||||
|
UpdateNotificationManager.showInstallFailedNotification(context, version, apkFile, "Install update permission was missing.")
|
||||||
|
|
||||||
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
|
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
|
||||||
data = "package:${context.packageName}".toUri()
|
data = "package:${context.packageName}".toUri()
|
||||||
@@ -72,13 +75,16 @@ object UpdateInstaller {
|
|||||||
session.fsync(sessionStream)
|
session.fsync(sessionStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = Intent(context, InstallReceiver::class.java)
|
val intent = Intent(context, InstallReceiver::class.java).apply {
|
||||||
|
putExtra(UpdateNotificationManager.EXTRA_VERSION, version)
|
||||||
|
putExtra(UpdateNotificationManager.EXTRA_APK_PATH, apkFile.absolutePath)
|
||||||
|
}
|
||||||
val pendingIntent = getBroadcast(context, 0, intent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT)
|
val pendingIntent = getBroadcast(context, 0, intent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT)
|
||||||
val statusReceiver = pendingIntent.intentSender
|
val statusReceiver = pendingIntent.intentSender
|
||||||
|
|
||||||
InstallReceiver.onReceiveResult.subscribe(this) { message ->
|
InstallReceiver.onReceiveResult.subscribe(this) { message ->
|
||||||
InstallReceiver.onReceiveResult.clear();
|
InstallReceiver.onReceiveResult.clear();
|
||||||
onReceiveResult(context, message);
|
onReceiveResult(context, version, apkFile, message);
|
||||||
};
|
};
|
||||||
Logger.i(TAG, "Committing install session for ${apkFile.absolutePath}")
|
Logger.i(TAG, "Committing install session for ${apkFile.absolutePath}")
|
||||||
session.commit(statusReceiver)
|
session.commit(statusReceiver)
|
||||||
@@ -88,6 +94,8 @@ object UpdateInstaller {
|
|||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
UIDialogs.toast(context, "Failed to install update: ${e.message}")
|
UIDialogs.toast(context, "Failed to install update: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateNotificationManager.showInstallFailedNotification(context, version, apkFile, e.message)
|
||||||
} finally {
|
} finally {
|
||||||
session?.close()
|
session?.close()
|
||||||
inputStream?.close()
|
inputStream?.close()
|
||||||
@@ -95,10 +103,20 @@ object UpdateInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onReceiveResult(context: Context, version: Int, apkFile: File, result: String?) {
|
||||||
|
try {
|
||||||
|
InstallReceiver.onReceiveResult.remove(this)
|
||||||
|
|
||||||
|
if (result.isNullOrEmpty()) {
|
||||||
private fun onReceiveResult(context: Context, result: String?) {
|
Logger.i(TAG, "Update install finished successfully")
|
||||||
InstallReceiver.onReceiveResult.remove(this);
|
UpdateNotificationManager.showInstallSucceededNotification(context, version)
|
||||||
UIDialogs.showGeneralErrorDialog(context, "Install failed due to:\n" + result);
|
} else {
|
||||||
|
Logger.w(TAG, "Update install failed: $result")
|
||||||
|
UpdateNotificationManager.showInstallFailedNotification(context, version, apkFile, result)
|
||||||
|
UIDialogs.showGeneralErrorDialog(context, "Install failed due to:\n$result")
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.e(TAG, "Failed to handle install result", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ object UpdateNotificationManager {
|
|||||||
const val NOTIF_ID_AVAILABLE = 2001
|
const val NOTIF_ID_AVAILABLE = 2001
|
||||||
const val NOTIF_ID_DOWNLOADING = 2002
|
const val NOTIF_ID_DOWNLOADING = 2002
|
||||||
const val NOTIF_ID_READY = 2003
|
const val NOTIF_ID_READY = 2003
|
||||||
|
const val NOTIF_ID_INSTALL_FAILED = 2004
|
||||||
|
const val NOTIF_ID_INSTALL_SUCCEEDED = 2005
|
||||||
|
|
||||||
fun ensureChannel(context: Context) {
|
fun ensureChannel(context: Context) {
|
||||||
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
@@ -49,6 +51,38 @@ object UpdateNotificationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showInstallSucceededNotification(context: Context, version: Int) {
|
||||||
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureChannel(context)
|
||||||
|
|
||||||
|
val launchIntent = context.packageManager
|
||||||
|
.getLaunchIntentForPackage(context.packageName)
|
||||||
|
?.apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
|
||||||
|
}
|
||||||
|
|
||||||
|
val launchPendingIntent = launchIntent?.let {
|
||||||
|
PendingIntent.getActivity(context, REQUEST_CODE_INSTALL, it, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.foreground)
|
||||||
|
.setContentTitle("Update installed")
|
||||||
|
.setContentText("Version $version installed. Tap to open.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setSilent(true)
|
||||||
|
|
||||||
|
if (launchPendingIntent != null) {
|
||||||
|
builder.setContentIntent(launchPendingIntent)
|
||||||
|
builder.addAction(0, "Open app", launchPendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManagerCompat.from(context).notify(NOTIF_ID_INSTALL_SUCCEEDED, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
fun showUpdateAvailableNotification(context: Context, version: Int) {
|
fun showUpdateAvailableNotification(context: Context, version: Int) {
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
@@ -78,6 +112,7 @@ object UpdateNotificationManager {
|
|||||||
.setContentText("A new version ($version) is available.")
|
.setContentText("A new version ($version) is available.")
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
.setContentIntent(yesPendingIntent)
|
||||||
.setSilent(true)
|
.setSilent(true)
|
||||||
.addAction(0, "Never", neverPendingIntent)
|
.addAction(0, "Never", neverPendingIntent)
|
||||||
.addAction(0, "Not now", noPendingIntent)
|
.addAction(0, "Not now", noPendingIntent)
|
||||||
@@ -104,7 +139,7 @@ object UpdateNotificationManager {
|
|||||||
.setSmallIcon(R.drawable.foreground)
|
.setSmallIcon(R.drawable.foreground)
|
||||||
.setContentTitle("Downloading update")
|
.setContentTitle("Downloading update")
|
||||||
.setContentText("Downloading version $version")
|
.setContentText("Downloading version $version")
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setSilent(true)
|
.setSilent(true)
|
||||||
.addAction(0, "Cancel", cancelPendingIntent)
|
.addAction(0, "Cancel", cancelPendingIntent)
|
||||||
@@ -141,6 +176,7 @@ object UpdateNotificationManager {
|
|||||||
.setContentTitle("Update downloaded")
|
.setContentTitle("Update downloaded")
|
||||||
.setContentText("Tap to install version $version.")
|
.setContentText("Tap to install version $version.")
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
.setContentIntent(installPendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSilent(true)
|
.setSilent(true)
|
||||||
.addAction(0, "Install", installPendingIntent)
|
.addAction(0, "Install", installPendingIntent)
|
||||||
@@ -166,9 +202,32 @@ object UpdateNotificationManager {
|
|||||||
NotificationManagerCompat.from(context).notify(NOTIF_ID_READY, builder.build())
|
NotificationManagerCompat.from(context).notify(NOTIF_ID_READY, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showInstallFailedNotification(context: Context, version: Int, apkFile: File, error: String?) {
|
||||||
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED)
|
||||||
|
return
|
||||||
|
|
||||||
|
ensureChannel(context)
|
||||||
|
|
||||||
|
val installIntent = InstallUpdateActivity.createIntent(context, version, apkFile.absolutePath)
|
||||||
|
val installPendingIntent = PendingIntent.getActivity(context, REQUEST_CODE_INSTALL, installIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.foreground)
|
||||||
|
.setContentTitle("Failed to install update")
|
||||||
|
.setContentText(if (error != null && error.isNotBlank()) "$error Tap to try again." else "Tap to try again.")
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setSilent(true)
|
||||||
|
.setContentIntent(installPendingIntent)
|
||||||
|
.addAction(0, "Install again", installPendingIntent)
|
||||||
|
|
||||||
|
NotificationManagerCompat.from(context).notify(NOTIF_ID_INSTALL_FAILED, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
fun cancelAll(context: Context) {
|
fun cancelAll(context: Context) {
|
||||||
NotificationManagerCompat.from(context).cancel(NOTIF_ID_AVAILABLE)
|
NotificationManagerCompat.from(context).cancel(NOTIF_ID_AVAILABLE)
|
||||||
NotificationManagerCompat.from(context).cancel(NOTIF_ID_DOWNLOADING)
|
NotificationManagerCompat.from(context).cancel(NOTIF_ID_DOWNLOADING)
|
||||||
NotificationManagerCompat.from(context).cancel(NOTIF_ID_READY)
|
NotificationManagerCompat.from(context).cancel(NOTIF_ID_READY)
|
||||||
|
NotificationManagerCompat.from(context).cancel(NOTIF_ID_INSTALL_FAILED)
|
||||||
|
NotificationManagerCompat.from(context).cancel(NOTIF_ID_INSTALL_SUCCEEDED)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class InstallUpdateActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
UpdateNotificationManager.cancelAll(this)
|
||||||
|
|
||||||
val version = intent.getIntExtra(UpdateNotificationManager.EXTRA_VERSION, 0)
|
val version = intent.getIntExtra(UpdateNotificationManager.EXTRA_VERSION, 0)
|
||||||
val apkPath = intent.getStringExtra(UpdateNotificationManager.EXTRA_APK_PATH)
|
val apkPath = intent.getStringExtra(UpdateNotificationManager.EXTRA_APK_PATH)
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ class InstallUpdateActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateInstaller.startInstall(this, apkFile)
|
UpdateInstaller.startInstall(this, version, apkFile)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.futo.platformplayer.R
|
|||||||
import com.futo.platformplayer.Settings
|
import com.futo.platformplayer.Settings
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
import com.futo.platformplayer.UpdateDownloadService
|
import com.futo.platformplayer.UpdateDownloadService
|
||||||
|
import com.futo.platformplayer.UpdateNotificationManager
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
import com.futo.platformplayer.copyToOutputStream
|
import com.futo.platformplayer.copyToOutputStream
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
@@ -64,12 +65,14 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) {
|
|||||||
_buttonShowChangelog = findViewById(R.id.button_show_changelog);
|
_buttonShowChangelog = findViewById(R.id.button_show_changelog);
|
||||||
|
|
||||||
_buttonNever.setOnClickListener {
|
_buttonNever.setOnClickListener {
|
||||||
|
UpdateNotificationManager.cancelAll(context)
|
||||||
Settings.instance.autoUpdate.check = 1;
|
Settings.instance.autoUpdate.check = 1;
|
||||||
Settings.instance.save();
|
Settings.instance.save();
|
||||||
dismiss();
|
dismiss();
|
||||||
};
|
};
|
||||||
|
|
||||||
_buttonClose.setOnClickListener {
|
_buttonClose.setOnClickListener {
|
||||||
|
UpdateNotificationManager.cancelAll(context)
|
||||||
dismiss();
|
dismiss();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,6 +82,8 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_buttonUpdate.setOnClickListener {
|
_buttonUpdate.setOnClickListener {
|
||||||
|
UpdateNotificationManager.cancelAll(context)
|
||||||
|
|
||||||
if (_updating) {
|
if (_updating) {
|
||||||
return@setOnClickListener;
|
return@setOnClickListener;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -600,38 +600,54 @@ class VideoDownload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun combineSegments(context: Context, segmentFiles: List<File>, targetFile: File) = withContext(Dispatchers.IO) {
|
private suspend fun combineSegments(context: Context, segmentFiles: List<File>, targetFile: File) = withContext(Dispatchers.IO) {
|
||||||
suspendCancellableCoroutine { continuation ->
|
require(segmentFiles.isNotEmpty()) { "segmentFiles must not be empty" }
|
||||||
val fileList = File(context.cacheDir, "fileList-${UUID.randomUUID()}.txt")
|
|
||||||
fileList.writeText(segmentFiles.joinToString("\n") { "file '${it.absolutePath}'" })
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
val concatInput = buildString {
|
||||||
|
append("concat:")
|
||||||
|
append(
|
||||||
|
segmentFiles.joinToString("|") { file ->
|
||||||
|
file.absolutePath
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val cmd = "-i \"$concatInput\" -c copy \"${targetFile.absolutePath}\""
|
||||||
|
|
||||||
val cmd = "-f concat -safe 0 -i \"${fileList.absolutePath}\" -c copy \"${targetFile.absolutePath}\""
|
|
||||||
val statisticsCallback = StatisticsCallback { _ ->
|
val statisticsCallback = StatisticsCallback { _ ->
|
||||||
//TODO: Show progress?
|
//No callback
|
||||||
}
|
}
|
||||||
|
|
||||||
val executorService = Executors.newSingleThreadExecutor()
|
val executorService = Executors.newSingleThreadExecutor()
|
||||||
val session = FFmpegKit.executeAsync(cmd,
|
|
||||||
{ session ->
|
val session = FFmpegKit.executeAsync(
|
||||||
if (ReturnCode.isSuccess(session.returnCode)) {
|
cmd,
|
||||||
fileList.delete()
|
{ completedSession ->
|
||||||
|
executorService.shutdown()
|
||||||
|
|
||||||
|
if (ReturnCode.isSuccess(completedSession.returnCode)) {
|
||||||
continuation.resumeWith(Result.success(Unit))
|
continuation.resumeWith(Result.success(Unit))
|
||||||
} else {
|
} else {
|
||||||
val errorMessage = if (ReturnCode.isCancel(session.returnCode)) {
|
val errorMessage = if (ReturnCode.isCancel(completedSession.returnCode)) {
|
||||||
"Command cancelled"
|
"Command cancelled"
|
||||||
} else {
|
} else {
|
||||||
"Command failed with state '${session.state}' and return code ${session.returnCode}, stack trace ${session.failStackTrace}"
|
"Command failed with state '${completedSession.state}' " +
|
||||||
|
"and return code ${completedSession.returnCode}, " +
|
||||||
|
"stack trace ${completedSession.failStackTrace}"
|
||||||
}
|
}
|
||||||
fileList.delete()
|
|
||||||
continuation.resumeWithException(RuntimeException(errorMessage))
|
continuation.resumeWithException(RuntimeException(errorMessage))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ Logger.v(TAG, it.message) },
|
{ log ->
|
||||||
|
Logger.v(TAG, log.message)
|
||||||
|
},
|
||||||
statisticsCallback,
|
statisticsCallback,
|
||||||
executorService
|
executorService
|
||||||
)
|
)
|
||||||
|
|
||||||
continuation.invokeOnCancellation {
|
continuation.invokeOnCancellation {
|
||||||
session.cancel()
|
session.cancel()
|
||||||
|
executorService.shutdownNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user