|
|
|
|
@ -7,6 +7,9 @@ import android.os.Binder |
|
|
|
|
import android.os.Environment |
|
|
|
|
import android.os.IBinder |
|
|
|
|
import androidx.core.content.FileProvider |
|
|
|
|
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL |
|
|
|
|
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS |
|
|
|
|
import com.arthenica.mobileffmpeg.FFmpeg |
|
|
|
|
import io.realm.Realm |
|
|
|
|
import kotlinx.coroutines.Dispatchers |
|
|
|
|
import kotlinx.coroutines.GlobalScope |
|
|
|
|
@ -16,6 +19,7 @@ import net.pokeranalytics.android.R |
|
|
|
|
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
|
|
|
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
|
|
|
|
import net.pokeranalytics.android.ui.extensions.toByteArray |
|
|
|
|
import net.pokeranalytics.android.util.FFMPEG_DESCRIPTOR_FILE |
|
|
|
|
import net.pokeranalytics.android.util.TriggerNotification |
|
|
|
|
import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted |
|
|
|
|
import net.pokeranalytics.android.util.extensions.findById |
|
|
|
|
@ -49,7 +53,7 @@ class ReplayExportService : Service() { |
|
|
|
|
|
|
|
|
|
fun videoExport(handHistoryId: String) { |
|
|
|
|
this@ReplayExportService.handHistoryId = handHistoryId |
|
|
|
|
startVideoExport() |
|
|
|
|
startFFMPEGVideoExport() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun gifExport(handHistoryId: String) { |
|
|
|
|
@ -115,6 +119,57 @@ class ReplayExportService : Service() { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun startFFMPEGVideoExport() { |
|
|
|
|
|
|
|
|
|
GlobalScope.launch(coroutineContext) { |
|
|
|
|
val async = GlobalScope.async { |
|
|
|
|
|
|
|
|
|
val realm = Realm.getDefaultInstance() |
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
|
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
|
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
|
|
|
|
|
val square = 1024 |
|
|
|
|
|
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
|
|
|
|
|
animator.setDimension(width.toFloat(), height.toFloat()) |
|
|
|
|
TableDrawer.configurePaints(context, animator) |
|
|
|
|
|
|
|
|
|
// generates all images and file descriptor |
|
|
|
|
Timber.d("Generating images for video...") |
|
|
|
|
val tmpDir = animator.generateVideoContent(this@ReplayExportService) |
|
|
|
|
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE" |
|
|
|
|
|
|
|
|
|
val directory = context.getExternalFilesDir(null) ?: throw PAIllegalStateException("File is invalid") |
|
|
|
|
val output = "${directory.path}/video_${Date().dateTimeFileFormatted}.mp4" |
|
|
|
|
|
|
|
|
|
Environment.getExternalStorageState(tmpDir) |
|
|
|
|
|
|
|
|
|
Timber.d("Assembling images for video...") |
|
|
|
|
FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vsync vfr -s ${width}x${height} -pix_fmt yuv420p $output") { id, rc -> |
|
|
|
|
|
|
|
|
|
if (rc == RETURN_CODE_SUCCESS) { |
|
|
|
|
Timber.d("FFMPEG command execution completed successfully") |
|
|
|
|
} else if (rc == RETURN_CODE_CANCEL) { |
|
|
|
|
Timber.d("Command execution cancelled by user.") |
|
|
|
|
} else { |
|
|
|
|
Timber.d(String.format("Command execution failed with rc=%d and the output below.", rc)) |
|
|
|
|
} |
|
|
|
|
tmpDir.delete() |
|
|
|
|
|
|
|
|
|
notifyUser(output) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
async.await() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun startVideoExport() { |
|
|
|
|
|
|
|
|
|
GlobalScope.launch(coroutineContext) { |
|
|
|
|
|