Skip to content

Instantly share code, notes, and snippets.

@ThomasGorisse
Last active March 17, 2023 12:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasGorisse/4840069d43c14b845791912c34cf44a5 to your computer and use it in GitHub Desktop.
Save ThomasGorisse/4840069d43c14b845791912c34cf44a5 to your computer and use it in GitHub Desktop.
SceneView Android screenshot and video screen recorder
package com.gorisse.thomas.arcamera
import android.Manifest
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.hardware.display.DisplayManager
import android.media.CamcorderProfile
import android.media.MediaRecorder
import android.net.Uri
import android.os.*
import android.provider.MediaStore
import android.view.PixelCopy
import android.view.Surface
import androidx.lifecycle.lifecycleScope
import com.touticom.ticfoundation.Intents
import com.touticom.ticfoundation.string
import com.touticom.ticfoundation.verifyPermission
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.max
import kotlin.math.min
object SceneViewRecorder {
private var mediaRecorder: MediaRecorder? = null
var recordFileDescriptor: ParcelFileDescriptor? = null
var recordUri: Uri? = null
fun takePhoto(fragment: Fragment) {
fragment.lifecycleScope.launchWhenCreated {
withContext(Dispatchers.IO) {
val context = fragment.requireContext()
val fileName = "${context.string(R.string.app_name)}-${
SimpleDateFormat(
"yyyyMMdd_HHmmss",
Locale.US
).format(Date())
}.jpg"
val values = ContentValues().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(
MediaStore.Images.Media.RELATIVE_PATH,
"${Environment.DIRECTORY_DCIM}/ARCamera"
)
}
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
}
val photoUri = fragment.requireContext().contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values
)!!
val photoFileDescriptor =
context.contentResolver.openFileDescriptor(photoUri, "w")!!
val bitmap = Bitmap.createBitmap(
fragment.binding.sceneView.width,
fragment.binding.sceneView.height,
Bitmap.Config.ARGB_8888
)
PixelCopy.request(
fragment.binding.sceneView, bitmap, { result ->
if (result == PixelCopy.SUCCESS) {
fragment.lifecycleScope.launchWhenCreated {
withContext(Dispatchers.IO) {
bitmap.compress(
Bitmap.CompressFormat.PNG,
100,
context.contentResolver.openOutputStream(photoUri)
)
}
withContext(Dispatchers.Main) {
fragment.setLoading(false)
fragment.isRecording = false
try {
fragment.activity.startActivity(
Intent(Intent.ACTION_SEND)
.setType("image/jpeg")
.putExtra(Intent.EXTRA_STREAM, photoUri)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.apply {
if (BuildConfig.SHARE_PACKAGE.isNotEmpty()) {
setPackage(BuildConfig.SHARE_PACKAGE)
}
}
)
} catch (e: Exception) {
Intents.view(
fragment.activity,
url = "https://play.google.com/store/apps/details?id=${BuildConfig.SHARE_PACKAGE}"
)
}
}
}
}
}, Handler(Looper.getMainLooper())
)
}
}
}
fun startVideoRecord(fragment: Fragment) {
verifyPermission(activity = fragment.activity,
permissions = listOf(
Manifest.permission.RECORD_AUDIO
),
fallBack = {
fragment.isRecording = false
},
success = {
val context = fragment.requireContext()
val fileName = "${context.string(R.string.app_name)}-${
SimpleDateFormat(
"yyyyMMdd_HHmmss",
Locale.US
).format(Date())
}.mp4"
val values = ContentValues().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(
MediaStore.Video.Media.RELATIVE_PATH,
"${Environment.DIRECTORY_MOVIES}/ARCamera"
)
}
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
put(MediaStore.Video.Media.IS_PENDING, 1)
}
recordUri = context.contentResolver.insert(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
values
)!!
recordFileDescriptor =
context.contentResolver.openFileDescriptor(recordUri!!, "w")!!
val supportedVideoQuality =
(fragment.videoQuality downTo 0).firstOrNull { CamcorderProfile.hasProfile(it) }
if (supportedVideoQuality != null) {
val profile = CamcorderProfile.get(supportedVideoQuality)
var (width, height) = 0 to 0
val isLandscape =
context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
if (isLandscape) {
width = min(
max(profile.videoFrameWidth, profile.videoFrameHeight),
max(fragment.binding.sceneView.width, fragment.binding.sceneView.height)
)
height = min(
min(profile.videoFrameWidth, profile.videoFrameHeight),
min(fragment.binding.sceneView.width, fragment.binding.sceneView.height)
)
} else {
width = min(
min(profile.videoFrameWidth, profile.videoFrameHeight),
min(fragment.binding.sceneView.width, fragment.binding.sceneView.height)
)
height = min(
max(profile.videoFrameWidth, profile.videoFrameHeight),
max(fragment.binding.sceneView.width, fragment.binding.sceneView.height)
)
}
mediaRecorder = MediaRecorder().apply {
val displayManager: DisplayManager =
context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val rotation = when (displayManager.displays[0].rotation) {
Surface.ROTATION_90 -> 270
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 90
else -> 0
}
setOrientationHint(rotation)
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(recordFileDescriptor!!.fileDescriptor)
setVideoEncodingBitRate(profile.videoBitRate)
setVideoFrameRate(profile.videoFrameRate)
setVideoSize(width, height)
setVideoEncoder(profile.videoCodec)
setAudioChannels(2)
setAudioSamplingRate(44100)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setAudioEncodingBitRate(128000)
}.also {
it.prepare()
it.start()
}
fragment.binding.sceneView.startMirroringToSurface(
mediaRecorder!!.surface,
0, 0,
width,
height
)
}
})
}
fun stopVideoRecord(fragment: Fragment) {
fragment.lifecycleScope.launchWhenCreated {
fragment.binding.sceneView.stopMirroringToSurface(mediaRecorder!!.surface)
mediaRecorder?.stop()
mediaRecorder?.reset()
recordFileDescriptor?.close()
fragment.context.contentResolver.update(recordUri!!, ContentValues().apply {
put(MediaStore.Video.Media.IS_PENDING, 0)
}, null, null)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment