|
|
|
|
@ -13,7 +13,9 @@ import android.provider.MediaStore |
|
|
|
|
import androidx.core.content.FileProvider |
|
|
|
|
import com.arthenica.ffmpegkit.FFmpegKit |
|
|
|
|
import io.realm.Realm |
|
|
|
|
import kotlinx.coroutines.* |
|
|
|
|
import kotlinx.coroutines.CoroutineScope |
|
|
|
|
import kotlinx.coroutines.Dispatchers |
|
|
|
|
import kotlinx.coroutines.launch |
|
|
|
|
import net.pokeranalytics.android.R |
|
|
|
|
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
|
|
|
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
|
|
|
|
@ -26,373 +28,321 @@ import timber.log.Timber |
|
|
|
|
import java.io.File |
|
|
|
|
import java.io.FileOutputStream |
|
|
|
|
import java.util.* |
|
|
|
|
import kotlin.coroutines.CoroutineContext |
|
|
|
|
|
|
|
|
|
enum class FileType(var value: String) { |
|
|
|
|
IMAGE_GIF("image/gif"), |
|
|
|
|
VIDEO_MP4("video/mp4") |
|
|
|
|
IMAGE_GIF("image/gif"), |
|
|
|
|
VIDEO_MP4("video/mp4") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class ReplayExportService : Service() { |
|
|
|
|
|
|
|
|
|
private lateinit var handHistoryId: String |
|
|
|
|
private lateinit var handHistoryId: String |
|
|
|
|
|
|
|
|
|
private val binder = LocalBinder() |
|
|
|
|
private val binder = LocalBinder() |
|
|
|
|
|
|
|
|
|
private val coroutineContext: CoroutineContext |
|
|
|
|
get() = Dispatchers.Main |
|
|
|
|
override fun onBind(intent: Intent?): IBinder { |
|
|
|
|
return binder |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override fun onBind(intent: Intent?): IBinder { |
|
|
|
|
return binder |
|
|
|
|
} |
|
|
|
|
inner class LocalBinder : Binder() { |
|
|
|
|
fun getService(): ReplayExportService = this@ReplayExportService |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
inner class LocalBinder : Binder() { |
|
|
|
|
fun getService(): ReplayExportService = this@ReplayExportService |
|
|
|
|
} |
|
|
|
|
fun videoExport(handHistoryId: String) { |
|
|
|
|
this@ReplayExportService.handHistoryId = handHistoryId |
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { |
|
|
|
|
startFFMPEGVideoExport() |
|
|
|
|
} else { |
|
|
|
|
startFFMPEGVideoExportPreQ() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun videoExport(handHistoryId: String) { |
|
|
|
|
this@ReplayExportService.handHistoryId = handHistoryId |
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { |
|
|
|
|
startFFMPEGVideoExport() |
|
|
|
|
} else { |
|
|
|
|
startFFMPEGVideoExportPreQ() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
fun gifExport(handHistoryId: String) { |
|
|
|
|
this@ReplayExportService.handHistoryId = handHistoryId |
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { |
|
|
|
|
startGIFExport() |
|
|
|
|
} else { |
|
|
|
|
startGIFExportPreQ() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun gifExport(handHistoryId: String) { |
|
|
|
|
this@ReplayExportService.handHistoryId = handHistoryId |
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { |
|
|
|
|
startGIFExport() |
|
|
|
|
} else { |
|
|
|
|
startGIFExportPreQ() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
private fun startGIFExport() { |
|
|
|
|
|
|
|
|
|
private fun startGIFExport() { |
|
|
|
|
CoroutineScope(Dispatchers.Default).launch { |
|
|
|
|
|
|
|
|
|
GlobalScope.launch(coroutineContext) { |
|
|
|
|
val c = GlobalScope.async { |
|
|
|
|
val realm = Realm.getDefaultInstance() |
|
|
|
|
realm.refresh() |
|
|
|
|
|
|
|
|
|
val realm = Realm.getDefaultInstance() |
|
|
|
|
realm.refresh() |
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) |
|
|
|
|
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
|
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
|
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
|
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
val square = 1024 |
|
|
|
|
|
|
|
|
|
val square = 1024 |
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
|
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), context) |
|
|
|
|
|
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), context) |
|
|
|
|
val formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val fileName = "hand_${formattedDate}" |
|
|
|
|
|
|
|
|
|
val formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val fileName = "hand_${formattedDate}" |
|
|
|
|
// Add a specific media item. |
|
|
|
|
val resolver = applicationContext.contentResolver |
|
|
|
|
|
|
|
|
|
// Add a specific media item. |
|
|
|
|
val resolver = applicationContext.contentResolver |
|
|
|
|
// Q version tested before calling the function |
|
|
|
|
val imageCollection = |
|
|
|
|
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) |
|
|
|
|
|
|
|
|
|
// Q version tested before calling the function |
|
|
|
|
val imageCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) |
|
|
|
|
val gifDetails = ContentValues().apply { |
|
|
|
|
put(MediaStore.Images.Media.DISPLAY_NAME, fileName) |
|
|
|
|
put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
val gifDetails = ContentValues().apply { |
|
|
|
|
put(MediaStore.Images.Media.DISPLAY_NAME, fileName) |
|
|
|
|
put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value) |
|
|
|
|
} |
|
|
|
|
val uri = resolver.insert(imageCollection, gifDetails) |
|
|
|
|
|
|
|
|
|
val uri = resolver.insert(imageCollection, gifDetails) |
|
|
|
|
if (uri != null) { |
|
|
|
|
|
|
|
|
|
if (uri != null) { |
|
|
|
|
val os = resolver.openOutputStream(uri) |
|
|
|
|
|
|
|
|
|
val os = resolver.openOutputStream(uri) |
|
|
|
|
val writer = AnimatedGIFWriter(false) |
|
|
|
|
writer.prepareForWrite(os, width, height) |
|
|
|
|
|
|
|
|
|
val writer = AnimatedGIFWriter(false) |
|
|
|
|
writer.prepareForWrite(os, width, height) |
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.configurePaints(context, animator) |
|
|
|
|
|
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.configurePaints(context, animator) |
|
|
|
|
var animationCount = 0 |
|
|
|
|
animator.frames(context) { bitmap, count -> |
|
|
|
|
|
|
|
|
|
var animationCount = 0 |
|
|
|
|
animator.frames(context) { bitmap, count -> |
|
|
|
|
when { |
|
|
|
|
count > 10 -> { |
|
|
|
|
writer.writeFrame(os, bitmap, count * 8) |
|
|
|
|
animationCount = 0 |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
if (animationCount % 2 == 0) { |
|
|
|
|
writer.writeFrame(os, bitmap) |
|
|
|
|
} |
|
|
|
|
animationCount++ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
writer.finishWrite(os) |
|
|
|
|
|
|
|
|
|
when { |
|
|
|
|
count > 10 -> { |
|
|
|
|
writer.writeFrame(os, bitmap, count * 8) |
|
|
|
|
animationCount = 0 |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
if (animationCount % 2 == 0) { |
|
|
|
|
writer.writeFrame(os, bitmap) |
|
|
|
|
} |
|
|
|
|
animationCount++ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
writer.finishWrite(os) |
|
|
|
|
realm.close() |
|
|
|
|
notifyUser(uri, FileType.IMAGE_GIF) |
|
|
|
|
} else { |
|
|
|
|
Timber.w("Resolver insert ended without uri...") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
realm.close() |
|
|
|
|
notifyUser(uri, FileType.IMAGE_GIF) |
|
|
|
|
} else { |
|
|
|
|
Timber.w("Resolver insert ended without uri...") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
c.await() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
private fun startFFMPEGVideoExport() { |
|
|
|
|
|
|
|
|
|
private fun startFFMPEGVideoExport() { |
|
|
|
|
CoroutineScope(Dispatchers.Default).launch { |
|
|
|
|
|
|
|
|
|
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 realm = Realm.getDefaultInstance() |
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
|
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
|
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
val square = 1024 |
|
|
|
|
|
|
|
|
|
val square = 1024 |
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
|
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) |
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.configurePaints(context, animator) |
|
|
|
|
|
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) |
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.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" |
|
|
|
|
|
|
|
|
|
// 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 formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val fileName = "hand_${formattedDate}.mp4" |
|
|
|
|
|
|
|
|
|
val formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val fileName = "hand_${formattedDate}.mp4" |
|
|
|
|
val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) |
|
|
|
|
?: throw PAIllegalStateException("File is invalid") |
|
|
|
|
val output = "${outputDirectory.path}/$fileName" |
|
|
|
|
|
|
|
|
|
val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: throw PAIllegalStateException("File is invalid") |
|
|
|
|
val output = "${outputDirectory.path}/$fileName" |
|
|
|
|
Timber.d("Assembling images for video...") |
|
|
|
|
|
|
|
|
|
Timber.d("Assembling images for video...") |
|
|
|
|
val command = |
|
|
|
|
"-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output" |
|
|
|
|
FFmpegKit.executeAsync(command) { |
|
|
|
|
|
|
|
|
|
val command = "-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output" |
|
|
|
|
FFmpegKit.executeAsync(command) { |
|
|
|
|
when { |
|
|
|
|
it.returnCode.isSuccess -> { |
|
|
|
|
Timber.d("FFMPEG command execution completed successfully") |
|
|
|
|
} |
|
|
|
|
it.returnCode.isCancel -> { |
|
|
|
|
Timber.d("Command execution cancelled by user.") |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
Timber.d("Command execution failed with rc=${it.returnCode.value} and the output below.") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
when { |
|
|
|
|
it.returnCode.isSuccess -> { |
|
|
|
|
Timber.d("FFMPEG command execution completed successfully") |
|
|
|
|
} |
|
|
|
|
it.returnCode.isCancel -> { |
|
|
|
|
Timber.d("Command execution cancelled by user.") |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
Timber.d(String.format("Command execution failed with rc=%d and the output below.", it.returnCode.value)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
File(dpath).delete() |
|
|
|
|
tmpDir.delete() |
|
|
|
|
|
|
|
|
|
File(dpath).delete() |
|
|
|
|
tmpDir.delete() |
|
|
|
|
val file = File(output) |
|
|
|
|
|
|
|
|
|
val file = File(output) |
|
|
|
|
val resolver = applicationContext.contentResolver |
|
|
|
|
|
|
|
|
|
val resolver = applicationContext.contentResolver |
|
|
|
|
// Q version tested before calling the function |
|
|
|
|
val videoCollection = |
|
|
|
|
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) |
|
|
|
|
|
|
|
|
|
// Q version tested before calling the function |
|
|
|
|
val videoCollection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) |
|
|
|
|
val fileDetails = ContentValues().apply { |
|
|
|
|
Timber.d("set file details = $fileName") |
|
|
|
|
put(MediaStore.Video.Media.DISPLAY_NAME, fileName) |
|
|
|
|
put(MediaStore.Images.Media.MIME_TYPE, FileType.VIDEO_MP4.value) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
val fileDetails = ContentValues().apply { |
|
|
|
|
Timber.d("set file details = $fileName") |
|
|
|
|
put(MediaStore.Video.Media.DISPLAY_NAME, fileName) |
|
|
|
|
put(MediaStore.Images.Media.MIME_TYPE, FileType.VIDEO_MP4.value) |
|
|
|
|
} |
|
|
|
|
// copy video to nice path |
|
|
|
|
resolver.insert(videoCollection, fileDetails)?.let { uri -> |
|
|
|
|
|
|
|
|
|
// copy video to nice path |
|
|
|
|
resolver.insert(videoCollection, fileDetails)?.let { uri -> |
|
|
|
|
Timber.d("copy file at uri = $uri") |
|
|
|
|
|
|
|
|
|
Timber.d("copy file at uri = $uri") |
|
|
|
|
val os = resolver.openOutputStream(uri) |
|
|
|
|
os?.write(file.readBytes()) |
|
|
|
|
os?.close() |
|
|
|
|
|
|
|
|
|
val os = resolver.openOutputStream(uri) |
|
|
|
|
os?.write(file.readBytes()) |
|
|
|
|
os?.close() |
|
|
|
|
file.delete() // delete temp file |
|
|
|
|
|
|
|
|
|
file.delete() // delete temp file |
|
|
|
|
notifyUser(uri, FileType.VIDEO_MP4) |
|
|
|
|
|
|
|
|
|
notifyUser(uri, FileType.VIDEO_MP4) |
|
|
|
|
} ?: run { |
|
|
|
|
Timber.w("Resolver insert ended without uri...") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} ?: run { |
|
|
|
|
Timber.w("Resolver insert ended without uri...") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
async.await() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
private fun startGIFExportPreQ() { |
|
|
|
|
|
|
|
|
|
// private fun startVideoExport() { |
|
|
|
|
// |
|
|
|
|
// GlobalScope.launch(coroutineContext) { |
|
|
|
|
// val c = 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) |
|
|
|
|
// |
|
|
|
|
// val muxer = MMediaMuxer() |
|
|
|
|
// muxer.init(null, width, height, "hhVideo", "YES!") |
|
|
|
|
// |
|
|
|
|
// animator.frames(context) { bitmap, count -> |
|
|
|
|
// |
|
|
|
|
// try { |
|
|
|
|
// val byteArray = bitmap.toByteArray() |
|
|
|
|
// muxer.addFrame(byteArray, count, false) |
|
|
|
|
// } catch (e: Exception) { |
|
|
|
|
// Timber.e("error = ${e.message}") |
|
|
|
|
// } |
|
|
|
|
// |
|
|
|
|
// } |
|
|
|
|
// |
|
|
|
|
// realm.close() |
|
|
|
|
// |
|
|
|
|
// muxer.createVideo { path -> |
|
|
|
|
// notifyUser(path) |
|
|
|
|
// } |
|
|
|
|
// |
|
|
|
|
// } |
|
|
|
|
// c.await() |
|
|
|
|
// } |
|
|
|
|
// |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
private fun startGIFExportPreQ() { |
|
|
|
|
|
|
|
|
|
GlobalScope.launch(coroutineContext) { |
|
|
|
|
val c = GlobalScope.async { |
|
|
|
|
|
|
|
|
|
val realm = Realm.getDefaultInstance() |
|
|
|
|
realm.refresh() |
|
|
|
|
CoroutineScope(Dispatchers.Default).launch { |
|
|
|
|
|
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
val realm = Realm.getDefaultInstance() |
|
|
|
|
realm.refresh() |
|
|
|
|
|
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) |
|
|
|
|
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
|
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
|
|
|
|
|
val square = 1024 |
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
|
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
val square = 1024 |
|
|
|
|
|
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), context) |
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
|
|
|
|
|
val formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val path = File( |
|
|
|
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), |
|
|
|
|
"hand_${formattedDate}.gif" |
|
|
|
|
).toString() |
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), context) |
|
|
|
|
|
|
|
|
|
val writer = AnimatedGIFWriter(false) |
|
|
|
|
val os = FileOutputStream(path) |
|
|
|
|
writer.prepareForWrite(os, width, height) |
|
|
|
|
val formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val path = File( |
|
|
|
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), |
|
|
|
|
"hand_${formattedDate}.gif" |
|
|
|
|
).toString() |
|
|
|
|
|
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.configurePaints(context, animator) |
|
|
|
|
val writer = AnimatedGIFWriter(false) |
|
|
|
|
val os = FileOutputStream(path) |
|
|
|
|
writer.prepareForWrite(os, width, height) |
|
|
|
|
|
|
|
|
|
var animationCount = 0 |
|
|
|
|
animator.frames(context) { bitmap, count -> |
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.configurePaints(context, animator) |
|
|
|
|
|
|
|
|
|
when { |
|
|
|
|
count > 10 -> { |
|
|
|
|
writer.writeFrame(os, bitmap, count * 8) |
|
|
|
|
animationCount = 0 |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
if (animationCount % 2 == 0) { |
|
|
|
|
writer.writeFrame(os, bitmap) |
|
|
|
|
} |
|
|
|
|
animationCount++ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
var animationCount = 0 |
|
|
|
|
animator.frames(context) { bitmap, count -> |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
writer.finishWrite(os) |
|
|
|
|
when { |
|
|
|
|
count > 10 -> { |
|
|
|
|
writer.writeFrame(os, bitmap, count * 8) |
|
|
|
|
animationCount = 0 |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
if (animationCount % 2 == 0) { |
|
|
|
|
writer.writeFrame(os, bitmap) |
|
|
|
|
} |
|
|
|
|
animationCount++ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
realm.close() |
|
|
|
|
notifyUser(path) |
|
|
|
|
} |
|
|
|
|
writer.finishWrite(os) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
c.await() |
|
|
|
|
} |
|
|
|
|
realm.close() |
|
|
|
|
notifyUser(path) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun startFFMPEGVideoExportPreQ() { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
GlobalScope.launch(coroutineContext) { |
|
|
|
|
val async = GlobalScope.async { |
|
|
|
|
private fun startFFMPEGVideoExportPreQ() { |
|
|
|
|
|
|
|
|
|
val realm = Realm.getDefaultInstance() |
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
CoroutineScope(Dispatchers.Default).launch { |
|
|
|
|
|
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
val realm = Realm.getDefaultInstance() |
|
|
|
|
val handHistory = realm.findById<HandHistory>(handHistoryId) |
|
|
|
|
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") |
|
|
|
|
|
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
val context = this@ReplayExportService |
|
|
|
|
|
|
|
|
|
val square = 1024 |
|
|
|
|
val animator = ReplayerAnimator(handHistory, true) |
|
|
|
|
|
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
val square = 1024 |
|
|
|
|
|
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) |
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.configurePaints(context, animator) |
|
|
|
|
val width = square |
|
|
|
|
val height = square |
|
|
|
|
|
|
|
|
|
// 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" |
|
|
|
|
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService) |
|
|
|
|
val drawer = TableDrawer() |
|
|
|
|
drawer.configurePaints(context, animator) |
|
|
|
|
|
|
|
|
|
val formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val output = File( |
|
|
|
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), |
|
|
|
|
"hand_${formattedDate}.mp4" |
|
|
|
|
).path |
|
|
|
|
// 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" |
|
|
|
|
|
|
|
|
|
Environment.getExternalStorageState(tmpDir) |
|
|
|
|
val formattedDate = Date().dateTimeFileFormatted |
|
|
|
|
val output = File( |
|
|
|
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), |
|
|
|
|
"hand_${formattedDate}.mp4" |
|
|
|
|
).path |
|
|
|
|
|
|
|
|
|
Timber.d("Assembling images for video...") |
|
|
|
|
Environment.getExternalStorageState(tmpDir) |
|
|
|
|
|
|
|
|
|
Timber.d("Assembling images for video...") |
|
|
|
|
|
|
|
|
|
val command = "-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output" |
|
|
|
|
FFmpegKit.executeAsync(command) { |
|
|
|
|
val command = |
|
|
|
|
"-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output" |
|
|
|
|
FFmpegKit.executeAsync(command) { |
|
|
|
|
|
|
|
|
|
when { |
|
|
|
|
it.returnCode.isSuccess -> { |
|
|
|
|
Timber.d("FFMPEG command execution completed successfully") |
|
|
|
|
} |
|
|
|
|
it.returnCode.isCancel -> { |
|
|
|
|
Timber.d("Command execution cancelled by user.") |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
Timber.d(String.format("Command execution failed with rc=%d and the output below.", it.returnCode.value)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
when { |
|
|
|
|
it.returnCode.isSuccess -> { |
|
|
|
|
Timber.d("FFMPEG command execution completed successfully") |
|
|
|
|
} |
|
|
|
|
it.returnCode.isCancel -> { |
|
|
|
|
Timber.d("Command execution cancelled by user.") |
|
|
|
|
} |
|
|
|
|
else -> { |
|
|
|
|
Timber.d("Command execution failed with rc=${it.returnCode.value} and the output below.") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output") { id, rc -> |
|
|
|
|
// |
|
|
|
|
@ -403,61 +353,60 @@ class ReplayExportService : Service() { |
|
|
|
|
// } else { |
|
|
|
|
// Timber.d(String.format("Command execution failed with rc=%d and the output below.", rc)) |
|
|
|
|
// } |
|
|
|
|
// Delete descriptor and image files |
|
|
|
|
// Delete descriptor and image files |
|
|
|
|
// tmpDir.delete() |
|
|
|
|
// File(dpath).delete() |
|
|
|
|
|
|
|
|
|
notifyUser(output) |
|
|
|
|
} |
|
|
|
|
notifyUser(output) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
async.await() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun notifyUser(uri: Uri, type: FileType) { |
|
|
|
|
private fun notifyUser(uri: Uri, type: FileType) { |
|
|
|
|
|
|
|
|
|
val title = getString(R.string.video_available) |
|
|
|
|
val body = getString(R.string.video_retrieval_message) + ": " + uri.path |
|
|
|
|
val title = getString(R.string.video_available) |
|
|
|
|
val body = getString(R.string.video_retrieval_message) + ": " + uri.path |
|
|
|
|
|
|
|
|
|
this.showNotification(title, body, uri, type.value) |
|
|
|
|
this.showNotification(title, body, uri, type.value) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun notifyUser(path: String) { |
|
|
|
|
private fun notifyUser(path: String) { |
|
|
|
|
|
|
|
|
|
val title = getString(R.string.video_available) |
|
|
|
|
val body = getString(R.string.video_retrieval_message) + ": " + path |
|
|
|
|
val title = getString(R.string.video_available) |
|
|
|
|
val body = getString(R.string.video_retrieval_message) + ": " + path |
|
|
|
|
|
|
|
|
|
Timber.d("Show local notification, path of file: ${path}") |
|
|
|
|
Timber.d("Show local notification, path of file: ${path}") |
|
|
|
|
|
|
|
|
|
val uri = FileProvider.getUriForFile( |
|
|
|
|
this, |
|
|
|
|
this.applicationContext.packageName.toString() + ".fileprovider", |
|
|
|
|
File(path) |
|
|
|
|
) |
|
|
|
|
val uri = FileProvider.getUriForFile( |
|
|
|
|
this, |
|
|
|
|
this.applicationContext.packageName.toString() + ".fileprovider", |
|
|
|
|
File(path) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
val type = when { |
|
|
|
|
path.contains("gif") -> "image/gif" |
|
|
|
|
else -> "video/*" |
|
|
|
|
} |
|
|
|
|
val type = when { |
|
|
|
|
path.contains("gif") -> "image/gif" |
|
|
|
|
else -> "video/*" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.showNotification(title, body, uri, type) |
|
|
|
|
} |
|
|
|
|
this.showNotification(title, body, uri, type) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun showNotification(title: String, body: String, uri: Uri, type: String) { |
|
|
|
|
private fun showNotification(title: String, body: String, uri: Uri, type: String) { |
|
|
|
|
|
|
|
|
|
val intent = Intent(Intent.ACTION_VIEW) |
|
|
|
|
intent.setDataAndType(uri, type) |
|
|
|
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK |
|
|
|
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
|
|
|
|
val chooser = Intent.createChooser(intent, getString(R.string.open_file_with)) |
|
|
|
|
val intent = Intent(Intent.ACTION_VIEW) |
|
|
|
|
intent.setDataAndType(uri, type) |
|
|
|
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK |
|
|
|
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) |
|
|
|
|
val chooser = Intent.createChooser(intent, getString(R.string.open_file_with)) |
|
|
|
|
|
|
|
|
|
val pendingIntent = PendingIntent.getActivity(this, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT) |
|
|
|
|
val pendingIntent = |
|
|
|
|
PendingIntent.getActivity(this, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT) |
|
|
|
|
|
|
|
|
|
TriggerNotification(this, title, body, pendingIntent) |
|
|
|
|
TriggerNotification(this, title, body, pendingIntent) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |