Adds pre Q and post Q hand history exports

blinds
Laurent 5 years ago
parent f874f35ad9
commit 55b15780d1
  1. 3
      app/src/main/AndroidManifest.xml
  2. 221
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt

@ -6,7 +6,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<application
android:name=".PokerAnalyticsApplication"

@ -10,6 +10,7 @@ import android.os.Build
import android.os.Environment
import android.os.IBinder
import android.provider.MediaStore
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
@ -25,6 +26,7 @@ import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.video.AnimatedGIFWriter
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.coroutines.CoroutineContext
@ -47,19 +49,25 @@ class ReplayExportService : Service() {
}
inner class LocalBinder : Binder() {
fun getService(): ReplayExportService = this@ReplayExportService
}
fun videoExport(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId
startFFMPEGVideoExport()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startFFMPEGVideoExport()
} else {
startFFMPEGVideoExportPreQ()
}
}
fun gifExport(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId
startGIFExport()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startGIFExport()
} else {
startGIFExportPreQ()
}
}
private fun startGIFExport() {
@ -89,22 +97,17 @@ class ReplayExportService : Service() {
// Add a specific media item.
val resolver = applicationContext.contentResolver
// Find all video files on the primary external storage device.
val videoCollection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
// 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, "image/gif")
}
val uri = resolver.insert(videoCollection, gifDetails)
val uri = resolver.insert(imageCollection, gifDetails)
if (uri != null) {
val os = resolver.openOutputStream(uri)
val writer = AnimatedGIFWriter(false)
@ -128,12 +131,13 @@ class ReplayExportService : Service() {
animationCount++
}
}
}
writer.finishWrite(os)
realm.close()
notifyUser(uri, FileType.IMAGE)
} else {
Timber.w("Resolver insert ended without uri...")
}
}
c.await()
@ -159,7 +163,6 @@ class ReplayExportService : Service() {
val height = square
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService)
// animator.setDimension()
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
@ -170,16 +173,10 @@ class ReplayExportService : Service() {
val formattedDate = Date().dateTimeFileFormatted
val fileName = "hand_${formattedDate}.mp4"
// val output = File(
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
// fileName
// ).path
val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: throw PAIllegalStateException("File is invalid")
val output = "${outputDirectory.path}/$fileName"
// Environment.getExternalStorageState(tmpDir)
Timber.d("Assembling images for video...")
FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -pix_fmt yuv420p $output") { id, rc ->
@ -196,24 +193,22 @@ class ReplayExportService : Service() {
}
}
File(dpath).delete()
tmpDir.delete()
val file = File(output)
val resolver = applicationContext.contentResolver
val videoCollection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
// 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.Video.Media.CONTENT_TYPE, "video/mp4")
}
// copy video to nice path
resolver.insert(videoCollection, fileDetails)?.let { uri ->
Timber.d("copy file at uri = $uri")
@ -222,7 +217,7 @@ class ReplayExportService : Service() {
os?.write(file.readBytes())
os?.close()
file.delete()
file.delete() // delete temp file
notifyUser(uri, FileType.VIDEO)
@ -230,17 +225,6 @@ class ReplayExportService : Service() {
Timber.w("Resolver insert ended without uri...")
}
// val uri = FileProvider.getUriForFile(
// applicationContext,
// applicationContext.packageName.toString() + ".fileprovider",
// File(output)
// )
// Timber.d("File exported at $output")
// notifyUser(output)
}
}
@ -295,26 +279,159 @@ class ReplayExportService : Service() {
//
// }
private fun startGIFExportPreQ() {
GlobalScope.launch(coroutineContext) {
val c = GlobalScope.async {
val realm = Realm.getDefaultInstance()
realm.refresh()
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.configure(width.toFloat(), height.toFloat(), context)
val formattedDate = Date().dateTimeFileFormatted
val path = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.gif"
).toString()
val writer = AnimatedGIFWriter(false)
val os = FileOutputStream(path)
writer.prepareForWrite(os, width, height)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
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)
realm.close()
notifyUser(path)
}
c.await()
}
}
private fun startFFMPEGVideoExportPreQ() {
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.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"
val formattedDate = Date().dateTimeFileFormatted
val output = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.mp4"
).path
Environment.getExternalStorageState(tmpDir)
Timber.d("Assembling images for video...")
FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vb 20M -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))
}
// Delete descriptor and image files
tmpDir.delete()
File(dpath).delete()
notifyUser(output)
}
}
async.await()
}
}
private fun notifyUser(uri: Uri, type: FileType) {
val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + uri.path
Timber.d("Show local notification, path of file: ${uri.path}")
this.showNotification(title, body, uri, type.value)
// val uri = FileProvider.getUriForFile(
// this,
// this.applicationContext.packageName.toString() + ".fileprovider",
// File(path)
// )
//
// val type = when {
// path.contains("gif") -> "image/gif"
// else -> "video/*"
// }
}
private fun notifyUser(path: String) {
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}")
val uri = FileProvider.getUriForFile(
this,
this.applicationContext.packageName.toString() + ".fileprovider",
File(path)
)
val type = when {
path.contains("gif") -> "image/gif"
else -> "video/*"
}
this.showNotification(title, body, uri, type)
}
private fun showNotification(title: String, body: String, uri: Uri, type: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, type.value)
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))

Loading…
Cancel
Save