214 lines
8.1 KiB
Kotlin
214 lines
8.1 KiB
Kotlin
package com.urkob.wittrail_android
|
|
|
|
import android.app.Notification
|
|
import android.app.NotificationChannel
|
|
import android.app.NotificationManager
|
|
import android.app.PendingIntent
|
|
import android.content.ContentValues
|
|
import android.content.Intent
|
|
import android.os.Build
|
|
import android.provider.MediaStore
|
|
import android.util.Log
|
|
import android.widget.Toast
|
|
import androidx.camera.core.CameraSelector
|
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
|
import androidx.camera.video.MediaStoreOutputOptions
|
|
import androidx.camera.video.Quality
|
|
import androidx.camera.video.QualitySelector
|
|
import androidx.camera.video.Recorder
|
|
import androidx.camera.video.Recording
|
|
import androidx.camera.video.VideoCapture
|
|
import androidx.camera.video.VideoRecordEvent
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.lifecycle.LifecycleService
|
|
import com.google.common.util.concurrent.ListenableFuture
|
|
import java.text.SimpleDateFormat
|
|
import java.util.Locale
|
|
import java.util.concurrent.ExecutorService
|
|
import java.util.concurrent.Executors
|
|
|
|
|
|
class RecordingService : LifecycleService() {
|
|
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
|
|
private val tag = "RecordingService"
|
|
private val fileNameFormat = "yyyy-MM-dd-HH-mm-ss-SSS"
|
|
private var useFrontCamera = false
|
|
private var videoCapture: VideoCapture<Recorder>? = null
|
|
private var recording: Recording? = null
|
|
private lateinit var cameraExecutor: ExecutorService
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
createNotificationChannel()
|
|
startForeground(NOTIFICATION_ID, createNotification())
|
|
|
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
|
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
|
|
|
Log.e(tag, "onCreate")
|
|
cameraProviderFuture.addListener({
|
|
// Initialize videoCapture here
|
|
val recorder = Recorder.Builder()
|
|
.setQualitySelector(QualitySelector.from(Quality.HIGHEST))
|
|
.build()
|
|
|
|
videoCapture = VideoCapture.withOutput(recorder)
|
|
|
|
// Now we can start the camera
|
|
startCamera()
|
|
}, ContextCompat.getMainExecutor(this))
|
|
|
|
Log.e(tag, "END onCreate")
|
|
}
|
|
|
|
private fun createNotificationChannel() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
val serviceChannel = NotificationChannel(
|
|
CHANNEL_ID,
|
|
"Foreground Service Channel",
|
|
NotificationManager.IMPORTANCE_DEFAULT
|
|
)
|
|
val manager = getSystemService(NotificationManager::class.java)
|
|
manager.createNotificationChannel(serviceChannel)
|
|
}
|
|
}
|
|
|
|
private fun createNotification(): Notification {
|
|
val notificationIntent = Intent(this, MainActivity::class.java)
|
|
val pendingIntent = PendingIntent.getActivity(
|
|
this,
|
|
0,
|
|
notificationIntent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
)
|
|
|
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
|
.setContentTitle("Recording Service")
|
|
.setContentText("Recording is running in the background")
|
|
.setSmallIcon(R.drawable.ic_launcher_foreground) // Replace with your own drawable resource
|
|
.setContentIntent(pendingIntent)
|
|
.setTicker("Ticker text")
|
|
.build()
|
|
}
|
|
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
useFrontCamera = intent?.getBooleanExtra("useFrontCamera", false) ?: false
|
|
when (intent?.action) {
|
|
ACTION_START_RECORDING -> {
|
|
Log.e(tag, ACTION_START_RECORDING)
|
|
captureVideo()
|
|
}
|
|
ACTION_STOP_RECORDING -> {
|
|
Log.e(tag, ACTION_STOP_RECORDING)
|
|
|
|
stopRecording()
|
|
// return START_NOT_STICKY
|
|
}
|
|
}
|
|
// return START_STICKY
|
|
return super.onStartCommand(intent, flags, startId)
|
|
}
|
|
|
|
// override fun onDestroy() {
|
|
// Log.e(tag, "ON DESTROY IS CALLED SHOUlD STOP CAMERA")
|
|
// super.onDestroy()
|
|
// }
|
|
|
|
private fun startCamera() {
|
|
Log.e(tag, "startCamera")
|
|
// Used to bind the lifecycle of cameras to the lifecycle owner
|
|
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
|
val cameraSelector = if (useFrontCamera) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
|
|
try {
|
|
// Unbind use cases before rebinding
|
|
cameraProvider.unbindAll()
|
|
|
|
// Bind use cases to camera
|
|
cameraProvider.bindToLifecycle(this, cameraSelector, videoCapture)
|
|
|
|
} catch(exc: Exception) {
|
|
Log.e(tag, "Use case binding failed", exc)
|
|
}
|
|
}
|
|
|
|
// Implements VideoCapture use case, including start and stop capturing.
|
|
private fun captureVideo() {
|
|
Log.e(tag, "START CAPTURE VIDEO")
|
|
val videoCapture = this.videoCapture ?: return
|
|
|
|
val curRecording = recording
|
|
if (curRecording != null) {
|
|
Log.e(tag, "curRecording != null")
|
|
// Stop the current recording session.
|
|
curRecording.stop()
|
|
recording = null
|
|
return
|
|
}
|
|
|
|
// create and start a new recording session
|
|
val name = SimpleDateFormat(fileNameFormat, Locale.US)
|
|
.format(System.currentTimeMillis())
|
|
val contentValues = ContentValues().apply {
|
|
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
|
|
put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/wittrail")
|
|
}
|
|
}
|
|
Log.e(tag, "contentValues")
|
|
val mediaStoreOutputOptions = MediaStoreOutputOptions
|
|
.Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
|
|
.setContentValues(contentValues)
|
|
.build()
|
|
Log.e(tag, "mediaStoreOutputOptions")
|
|
recording = videoCapture.output
|
|
.prepareRecording(this, mediaStoreOutputOptions)
|
|
.start(ContextCompat.getMainExecutor(this)) { recordEvent ->
|
|
when(recordEvent) {
|
|
is VideoRecordEvent.Start -> {
|
|
// TODO: should only display stop button
|
|
Log.e(tag, "START RECORDING")
|
|
}
|
|
is VideoRecordEvent.Finalize -> {
|
|
Log.e(tag, "ON FINALIZE")
|
|
if (!recordEvent.hasError()) {
|
|
Log.d(tag, "Saved URI: " + recordEvent.outputResults.outputUri)
|
|
val msg = "Video capture succeeded: " +
|
|
"${recordEvent.outputResults.outputUri}"
|
|
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT)
|
|
.show()
|
|
Log.d(tag, msg)
|
|
} else {
|
|
Log.e(tag, "Video capture ends with error: " +
|
|
"${recordEvent.error} $recordEvent")
|
|
Log.d(tag, "Saved URI: " + recordEvent.outputResults.outputUri)
|
|
recording?.close()
|
|
recording = null
|
|
}
|
|
Log.e(tag, "RECORDING STOPPED")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
companion object {
|
|
const val ACTION_START_RECORDING = "com.urkob.wittrail_android.action.START_RECORDING"
|
|
const val ACTION_STOP_RECORDING = "com.urkob.wittrail_android.action.STOP_RECORDING"
|
|
private const val CHANNEL_ID = "ForegroundServiceChannel"
|
|
private const val NOTIFICATION_ID = 1
|
|
}
|
|
private fun stopRecording() {
|
|
Log.e(tag, "stopRecording")
|
|
|
|
val curRecording = recording
|
|
if (curRecording != null) {
|
|
Log.e(tag, "stopRecording curRecording != null Stop the current recording session")
|
|
curRecording.stop()
|
|
recording = null
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|