diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt new file mode 100644 index 0000000..765db08 --- /dev/null +++ b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt @@ -0,0 +1,136 @@ +package com.casic.endoscope.service + +import android.app.Activity +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.MediaRecorder +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import com.casic.endoscope.R +import com.pengxh.kt.lite.extensions.getSystemService +import kotlinx.coroutines.Runnable + +class ScreenShortRecordService : Service() { + + private val kTag = "ScreenShortRecordService" + private var mediaProjection: MediaProjection? = null + private var recorder: MediaRecorder? = null + private var virtualDisplay: VirtualDisplay? = null + + inner class ScreenShortRecordServiceBinder : Binder() { + fun getScreenShortRecordService(): ScreenShortRecordService { + return this@ScreenShortRecordService + } + } + + override fun onBind(intent: Intent?): IBinder { + return ScreenShortRecordServiceBinder() + } + + fun startRecorder(videoPath: String, intent: Intent) { + Log.d(kTag, "startRecorder: 开始录屏 $videoPath") + //开启通知,并申请成为前台服务 + createForegroundNotification() + + val mediaProjectionManager = getSystemService() + //获得令牌 + mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, intent) + Handler(Looper.myLooper()!!).postDelayed(Runnable { + //配置MediaRecorder + if (configMediaRecorder(videoPath)) { + try { + //开始录屏 + recorder?.start() + } catch (e: Exception) { + e.printStackTrace() + } + } + }, 400) + } + + private fun createForegroundNotification() { + val notificationManager = getSystemService() + val builder: Notification.Builder + val name = resources.getString(R.string.app_name) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //创建渠道 + val id = "${kTag}Channel" + val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) + channel.setShowBadge(true) + channel.enableVibration(false) + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 + notificationManager?.createNotificationChannel(channel) + builder = Notification.Builder(this, id) + } else { + builder = Notification.Builder(this) + } + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + builder.setContentTitle(name) + .setContentText("${name}录屏中") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(bitmap) + val notification = builder.build() + notification.flags = Notification.FLAG_NO_CLEAR + startForeground(Int.MAX_VALUE, notification) + } + + private fun configMediaRecorder(videoPath: String): Boolean { + val dm = resources.displayMetrics + recorder = MediaRecorder() + recorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) //音频载体 + setVideoSource(MediaRecorder.VideoSource.SURFACE) //视频载体 + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) //输出格式 + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //音频格式 + setVideoEncoder(MediaRecorder.VideoEncoder.H264) //视频格式 + setVideoSize(dm.widthPixels, dm.heightPixels) //size + setVideoFrameRate(30) //帧率 + setVideoEncodingBitRate(3 * 1024 * 1024) //比特率 + //设置文件位置 + setOutputFile(videoPath) + try { + prepare() + virtualDisplay = mediaProjection?.createVirtualDisplay( + kTag, + dm.widthPixels, + dm.heightPixels, + dm.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, + null + ) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return true + } + + //停止录制 + fun stopRecorder() { + recorder?.stop() + recorder?.release() + recorder = null + + mediaProjection?.stop() + + //退出前台服务 + stopForeground(true) + Log.d(kTag, "stopRecorder: 结束录屏") + } +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt new file mode 100644 index 0000000..765db08 --- /dev/null +++ b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt @@ -0,0 +1,136 @@ +package com.casic.endoscope.service + +import android.app.Activity +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.MediaRecorder +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import com.casic.endoscope.R +import com.pengxh.kt.lite.extensions.getSystemService +import kotlinx.coroutines.Runnable + +class ScreenShortRecordService : Service() { + + private val kTag = "ScreenShortRecordService" + private var mediaProjection: MediaProjection? = null + private var recorder: MediaRecorder? = null + private var virtualDisplay: VirtualDisplay? = null + + inner class ScreenShortRecordServiceBinder : Binder() { + fun getScreenShortRecordService(): ScreenShortRecordService { + return this@ScreenShortRecordService + } + } + + override fun onBind(intent: Intent?): IBinder { + return ScreenShortRecordServiceBinder() + } + + fun startRecorder(videoPath: String, intent: Intent) { + Log.d(kTag, "startRecorder: 开始录屏 $videoPath") + //开启通知,并申请成为前台服务 + createForegroundNotification() + + val mediaProjectionManager = getSystemService() + //获得令牌 + mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, intent) + Handler(Looper.myLooper()!!).postDelayed(Runnable { + //配置MediaRecorder + if (configMediaRecorder(videoPath)) { + try { + //开始录屏 + recorder?.start() + } catch (e: Exception) { + e.printStackTrace() + } + } + }, 400) + } + + private fun createForegroundNotification() { + val notificationManager = getSystemService() + val builder: Notification.Builder + val name = resources.getString(R.string.app_name) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //创建渠道 + val id = "${kTag}Channel" + val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) + channel.setShowBadge(true) + channel.enableVibration(false) + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 + notificationManager?.createNotificationChannel(channel) + builder = Notification.Builder(this, id) + } else { + builder = Notification.Builder(this) + } + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + builder.setContentTitle(name) + .setContentText("${name}录屏中") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(bitmap) + val notification = builder.build() + notification.flags = Notification.FLAG_NO_CLEAR + startForeground(Int.MAX_VALUE, notification) + } + + private fun configMediaRecorder(videoPath: String): Boolean { + val dm = resources.displayMetrics + recorder = MediaRecorder() + recorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) //音频载体 + setVideoSource(MediaRecorder.VideoSource.SURFACE) //视频载体 + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) //输出格式 + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //音频格式 + setVideoEncoder(MediaRecorder.VideoEncoder.H264) //视频格式 + setVideoSize(dm.widthPixels, dm.heightPixels) //size + setVideoFrameRate(30) //帧率 + setVideoEncodingBitRate(3 * 1024 * 1024) //比特率 + //设置文件位置 + setOutputFile(videoPath) + try { + prepare() + virtualDisplay = mediaProjection?.createVirtualDisplay( + kTag, + dm.widthPixels, + dm.heightPixels, + dm.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, + null + ) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return true + } + + //停止录制 + fun stopRecorder() { + recorder?.stop() + recorder?.release() + recorder = null + + mediaProjection?.stop() + + //退出前台服务 + stopForeground(true) + Log.d(kTag, "stopRecorder: 结束录屏") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt b/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt deleted file mode 100644 index f54c2a2..0000000 --- a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.casic.endoscope.service - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import com.casic.endoscope.extensions.transcodeVideo -import com.casic.endoscope.utils.ProjectConstant -import java.util.Timer -import java.util.TimerTask - -class VideoTranscodeService : Service() { - - private val kTag = "VideoTranscodeService" - private var timer: Timer? = null - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - timer = Timer() - Log.d(kTag, "onCreate => $kTag") - } - - - /** - * 执行该方法后,Service 会启动并在后台无限期执行 - * 需要调用 stopSelf() 或 stopService() 来结束Service - * */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - //定时遍历缓存,看是否有新的视频被录制 - timer?.schedule(object : TimerTask() { - override fun run() { - //开始后台转码 - if (!ProjectConstant.isUnderDecodingVideo && ProjectConstant.VIDEO_PATH_STACK.isNotEmpty()) { - ProjectConstant.isUnderDecodingVideo = true - ProjectConstant.VIDEO_PATH_STACK.pop().transcodeVideo(kTag) - } else { - Log.d(kTag, "run: 转码中......") - } - } - }, 0, 1000) - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - Log.d(kTag, "onDestroy => $kTag") - timer?.cancel() - } -} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt new file mode 100644 index 0000000..765db08 --- /dev/null +++ b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt @@ -0,0 +1,136 @@ +package com.casic.endoscope.service + +import android.app.Activity +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.MediaRecorder +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import com.casic.endoscope.R +import com.pengxh.kt.lite.extensions.getSystemService +import kotlinx.coroutines.Runnable + +class ScreenShortRecordService : Service() { + + private val kTag = "ScreenShortRecordService" + private var mediaProjection: MediaProjection? = null + private var recorder: MediaRecorder? = null + private var virtualDisplay: VirtualDisplay? = null + + inner class ScreenShortRecordServiceBinder : Binder() { + fun getScreenShortRecordService(): ScreenShortRecordService { + return this@ScreenShortRecordService + } + } + + override fun onBind(intent: Intent?): IBinder { + return ScreenShortRecordServiceBinder() + } + + fun startRecorder(videoPath: String, intent: Intent) { + Log.d(kTag, "startRecorder: 开始录屏 $videoPath") + //开启通知,并申请成为前台服务 + createForegroundNotification() + + val mediaProjectionManager = getSystemService() + //获得令牌 + mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, intent) + Handler(Looper.myLooper()!!).postDelayed(Runnable { + //配置MediaRecorder + if (configMediaRecorder(videoPath)) { + try { + //开始录屏 + recorder?.start() + } catch (e: Exception) { + e.printStackTrace() + } + } + }, 400) + } + + private fun createForegroundNotification() { + val notificationManager = getSystemService() + val builder: Notification.Builder + val name = resources.getString(R.string.app_name) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //创建渠道 + val id = "${kTag}Channel" + val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) + channel.setShowBadge(true) + channel.enableVibration(false) + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 + notificationManager?.createNotificationChannel(channel) + builder = Notification.Builder(this, id) + } else { + builder = Notification.Builder(this) + } + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + builder.setContentTitle(name) + .setContentText("${name}录屏中") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(bitmap) + val notification = builder.build() + notification.flags = Notification.FLAG_NO_CLEAR + startForeground(Int.MAX_VALUE, notification) + } + + private fun configMediaRecorder(videoPath: String): Boolean { + val dm = resources.displayMetrics + recorder = MediaRecorder() + recorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) //音频载体 + setVideoSource(MediaRecorder.VideoSource.SURFACE) //视频载体 + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) //输出格式 + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //音频格式 + setVideoEncoder(MediaRecorder.VideoEncoder.H264) //视频格式 + setVideoSize(dm.widthPixels, dm.heightPixels) //size + setVideoFrameRate(30) //帧率 + setVideoEncodingBitRate(3 * 1024 * 1024) //比特率 + //设置文件位置 + setOutputFile(videoPath) + try { + prepare() + virtualDisplay = mediaProjection?.createVirtualDisplay( + kTag, + dm.widthPixels, + dm.heightPixels, + dm.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, + null + ) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return true + } + + //停止录制 + fun stopRecorder() { + recorder?.stop() + recorder?.release() + recorder = null + + mediaProjection?.stop() + + //退出前台服务 + stopForeground(true) + Log.d(kTag, "stopRecorder: 结束录屏") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt b/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt deleted file mode 100644 index f54c2a2..0000000 --- a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.casic.endoscope.service - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import com.casic.endoscope.extensions.transcodeVideo -import com.casic.endoscope.utils.ProjectConstant -import java.util.Timer -import java.util.TimerTask - -class VideoTranscodeService : Service() { - - private val kTag = "VideoTranscodeService" - private var timer: Timer? = null - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - timer = Timer() - Log.d(kTag, "onCreate => $kTag") - } - - - /** - * 执行该方法后,Service 会启动并在后台无限期执行 - * 需要调用 stopSelf() 或 stopService() 来结束Service - * */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - //定时遍历缓存,看是否有新的视频被录制 - timer?.schedule(object : TimerTask() { - override fun run() { - //开始后台转码 - if (!ProjectConstant.isUnderDecodingVideo && ProjectConstant.VIDEO_PATH_STACK.isNotEmpty()) { - ProjectConstant.isUnderDecodingVideo = true - ProjectConstant.VIDEO_PATH_STACK.pop().transcodeVideo(kTag) - } else { - Log.d(kTag, "run: 转码中......") - } - } - }, 0, 1000) - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - Log.d(kTag, "onDestroy => $kTag") - timer?.cancel() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt b/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt deleted file mode 100644 index 128fbd8..0000000 --- a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.endoscope.utils - -import io.microshow.rxffmpeg.RxFFmpegCommandList - -object FFmpegCommandHub { - - /** - * 海康视频转码指令 - * */ - fun createVideoTranscodeCommand(inputFilePath: String, outputFilePath: String): Array { - val commandParams = RxFFmpegCommandList() - commandParams.append("-i") - commandParams.append(inputFilePath) - commandParams.append("-c:v") - commandParams.append("libx264") - commandParams.append(outputFilePath) - return commandParams.build() - } -} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt new file mode 100644 index 0000000..765db08 --- /dev/null +++ b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt @@ -0,0 +1,136 @@ +package com.casic.endoscope.service + +import android.app.Activity +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.MediaRecorder +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import com.casic.endoscope.R +import com.pengxh.kt.lite.extensions.getSystemService +import kotlinx.coroutines.Runnable + +class ScreenShortRecordService : Service() { + + private val kTag = "ScreenShortRecordService" + private var mediaProjection: MediaProjection? = null + private var recorder: MediaRecorder? = null + private var virtualDisplay: VirtualDisplay? = null + + inner class ScreenShortRecordServiceBinder : Binder() { + fun getScreenShortRecordService(): ScreenShortRecordService { + return this@ScreenShortRecordService + } + } + + override fun onBind(intent: Intent?): IBinder { + return ScreenShortRecordServiceBinder() + } + + fun startRecorder(videoPath: String, intent: Intent) { + Log.d(kTag, "startRecorder: 开始录屏 $videoPath") + //开启通知,并申请成为前台服务 + createForegroundNotification() + + val mediaProjectionManager = getSystemService() + //获得令牌 + mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, intent) + Handler(Looper.myLooper()!!).postDelayed(Runnable { + //配置MediaRecorder + if (configMediaRecorder(videoPath)) { + try { + //开始录屏 + recorder?.start() + } catch (e: Exception) { + e.printStackTrace() + } + } + }, 400) + } + + private fun createForegroundNotification() { + val notificationManager = getSystemService() + val builder: Notification.Builder + val name = resources.getString(R.string.app_name) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //创建渠道 + val id = "${kTag}Channel" + val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) + channel.setShowBadge(true) + channel.enableVibration(false) + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 + notificationManager?.createNotificationChannel(channel) + builder = Notification.Builder(this, id) + } else { + builder = Notification.Builder(this) + } + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + builder.setContentTitle(name) + .setContentText("${name}录屏中") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(bitmap) + val notification = builder.build() + notification.flags = Notification.FLAG_NO_CLEAR + startForeground(Int.MAX_VALUE, notification) + } + + private fun configMediaRecorder(videoPath: String): Boolean { + val dm = resources.displayMetrics + recorder = MediaRecorder() + recorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) //音频载体 + setVideoSource(MediaRecorder.VideoSource.SURFACE) //视频载体 + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) //输出格式 + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //音频格式 + setVideoEncoder(MediaRecorder.VideoEncoder.H264) //视频格式 + setVideoSize(dm.widthPixels, dm.heightPixels) //size + setVideoFrameRate(30) //帧率 + setVideoEncodingBitRate(3 * 1024 * 1024) //比特率 + //设置文件位置 + setOutputFile(videoPath) + try { + prepare() + virtualDisplay = mediaProjection?.createVirtualDisplay( + kTag, + dm.widthPixels, + dm.heightPixels, + dm.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, + null + ) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return true + } + + //停止录制 + fun stopRecorder() { + recorder?.stop() + recorder?.release() + recorder = null + + mediaProjection?.stop() + + //退出前台服务 + stopForeground(true) + Log.d(kTag, "stopRecorder: 结束录屏") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt b/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt deleted file mode 100644 index f54c2a2..0000000 --- a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.casic.endoscope.service - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import com.casic.endoscope.extensions.transcodeVideo -import com.casic.endoscope.utils.ProjectConstant -import java.util.Timer -import java.util.TimerTask - -class VideoTranscodeService : Service() { - - private val kTag = "VideoTranscodeService" - private var timer: Timer? = null - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - timer = Timer() - Log.d(kTag, "onCreate => $kTag") - } - - - /** - * 执行该方法后,Service 会启动并在后台无限期执行 - * 需要调用 stopSelf() 或 stopService() 来结束Service - * */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - //定时遍历缓存,看是否有新的视频被录制 - timer?.schedule(object : TimerTask() { - override fun run() { - //开始后台转码 - if (!ProjectConstant.isUnderDecodingVideo && ProjectConstant.VIDEO_PATH_STACK.isNotEmpty()) { - ProjectConstant.isUnderDecodingVideo = true - ProjectConstant.VIDEO_PATH_STACK.pop().transcodeVideo(kTag) - } else { - Log.d(kTag, "run: 转码中......") - } - } - }, 0, 1000) - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - Log.d(kTag, "onDestroy => $kTag") - timer?.cancel() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt b/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt deleted file mode 100644 index 128fbd8..0000000 --- a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.endoscope.utils - -import io.microshow.rxffmpeg.RxFFmpegCommandList - -object FFmpegCommandHub { - - /** - * 海康视频转码指令 - * */ - fun createVideoTranscodeCommand(inputFilePath: String, outputFilePath: String): Array { - val commandParams = RxFFmpegCommandList() - commandParams.append("-i") - commandParams.append(inputFilePath) - commandParams.append("-c:v") - commandParams.append("libx264") - commandParams.append(outputFilePath) - return commandParams.build() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt index 02e57c2..78c2ac0 100644 --- a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt +++ b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt @@ -2,7 +2,6 @@ import android.Manifest import android.os.Build -import java.util.Stack object ProjectConstant { val USER_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -14,7 +13,8 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( @@ -23,21 +23,24 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } @@ -49,14 +52,5 @@ const val HK_NET_USERNAME = "admin" const val HK_NET_PASSWORD = "casic203" - //待转码视频路径 - val VIDEO_PATH_STACK = Stack() - const val SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb" //连接设备的UUID - - //视频正在转码 - var isUnderDecodingVideo = false - - //已转码得视频数量 - var decodedViewCount = 0 } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt new file mode 100644 index 0000000..765db08 --- /dev/null +++ b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt @@ -0,0 +1,136 @@ +package com.casic.endoscope.service + +import android.app.Activity +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.MediaRecorder +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import com.casic.endoscope.R +import com.pengxh.kt.lite.extensions.getSystemService +import kotlinx.coroutines.Runnable + +class ScreenShortRecordService : Service() { + + private val kTag = "ScreenShortRecordService" + private var mediaProjection: MediaProjection? = null + private var recorder: MediaRecorder? = null + private var virtualDisplay: VirtualDisplay? = null + + inner class ScreenShortRecordServiceBinder : Binder() { + fun getScreenShortRecordService(): ScreenShortRecordService { + return this@ScreenShortRecordService + } + } + + override fun onBind(intent: Intent?): IBinder { + return ScreenShortRecordServiceBinder() + } + + fun startRecorder(videoPath: String, intent: Intent) { + Log.d(kTag, "startRecorder: 开始录屏 $videoPath") + //开启通知,并申请成为前台服务 + createForegroundNotification() + + val mediaProjectionManager = getSystemService() + //获得令牌 + mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, intent) + Handler(Looper.myLooper()!!).postDelayed(Runnable { + //配置MediaRecorder + if (configMediaRecorder(videoPath)) { + try { + //开始录屏 + recorder?.start() + } catch (e: Exception) { + e.printStackTrace() + } + } + }, 400) + } + + private fun createForegroundNotification() { + val notificationManager = getSystemService() + val builder: Notification.Builder + val name = resources.getString(R.string.app_name) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //创建渠道 + val id = "${kTag}Channel" + val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) + channel.setShowBadge(true) + channel.enableVibration(false) + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 + notificationManager?.createNotificationChannel(channel) + builder = Notification.Builder(this, id) + } else { + builder = Notification.Builder(this) + } + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + builder.setContentTitle(name) + .setContentText("${name}录屏中") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(bitmap) + val notification = builder.build() + notification.flags = Notification.FLAG_NO_CLEAR + startForeground(Int.MAX_VALUE, notification) + } + + private fun configMediaRecorder(videoPath: String): Boolean { + val dm = resources.displayMetrics + recorder = MediaRecorder() + recorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) //音频载体 + setVideoSource(MediaRecorder.VideoSource.SURFACE) //视频载体 + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) //输出格式 + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //音频格式 + setVideoEncoder(MediaRecorder.VideoEncoder.H264) //视频格式 + setVideoSize(dm.widthPixels, dm.heightPixels) //size + setVideoFrameRate(30) //帧率 + setVideoEncodingBitRate(3 * 1024 * 1024) //比特率 + //设置文件位置 + setOutputFile(videoPath) + try { + prepare() + virtualDisplay = mediaProjection?.createVirtualDisplay( + kTag, + dm.widthPixels, + dm.heightPixels, + dm.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, + null + ) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return true + } + + //停止录制 + fun stopRecorder() { + recorder?.stop() + recorder?.release() + recorder = null + + mediaProjection?.stop() + + //退出前台服务 + stopForeground(true) + Log.d(kTag, "stopRecorder: 结束录屏") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt b/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt deleted file mode 100644 index f54c2a2..0000000 --- a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.casic.endoscope.service - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import com.casic.endoscope.extensions.transcodeVideo -import com.casic.endoscope.utils.ProjectConstant -import java.util.Timer -import java.util.TimerTask - -class VideoTranscodeService : Service() { - - private val kTag = "VideoTranscodeService" - private var timer: Timer? = null - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - timer = Timer() - Log.d(kTag, "onCreate => $kTag") - } - - - /** - * 执行该方法后,Service 会启动并在后台无限期执行 - * 需要调用 stopSelf() 或 stopService() 来结束Service - * */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - //定时遍历缓存,看是否有新的视频被录制 - timer?.schedule(object : TimerTask() { - override fun run() { - //开始后台转码 - if (!ProjectConstant.isUnderDecodingVideo && ProjectConstant.VIDEO_PATH_STACK.isNotEmpty()) { - ProjectConstant.isUnderDecodingVideo = true - ProjectConstant.VIDEO_PATH_STACK.pop().transcodeVideo(kTag) - } else { - Log.d(kTag, "run: 转码中......") - } - } - }, 0, 1000) - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - Log.d(kTag, "onDestroy => $kTag") - timer?.cancel() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt b/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt deleted file mode 100644 index 128fbd8..0000000 --- a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.endoscope.utils - -import io.microshow.rxffmpeg.RxFFmpegCommandList - -object FFmpegCommandHub { - - /** - * 海康视频转码指令 - * */ - fun createVideoTranscodeCommand(inputFilePath: String, outputFilePath: String): Array { - val commandParams = RxFFmpegCommandList() - commandParams.append("-i") - commandParams.append(inputFilePath) - commandParams.append("-c:v") - commandParams.append("libx264") - commandParams.append(outputFilePath) - return commandParams.build() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt index 02e57c2..78c2ac0 100644 --- a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt +++ b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt @@ -2,7 +2,6 @@ import android.Manifest import android.os.Build -import java.util.Stack object ProjectConstant { val USER_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -14,7 +13,8 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( @@ -23,21 +23,24 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } @@ -49,14 +52,5 @@ const val HK_NET_USERNAME = "admin" const val HK_NET_PASSWORD = "casic203" - //待转码视频路径 - val VIDEO_PATH_STACK = Stack() - const val SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb" //连接设备的UUID - - //视频正在转码 - var isUnderDecodingVideo = false - - //已转码得视频数量 - var decodedViewCount = 0 } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt b/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt index b4e6474..692f453 100644 --- a/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt +++ b/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt @@ -1,6 +1,5 @@ package com.casic.endoscope.view -import android.annotation.SuppressLint import android.os.Bundle import android.view.View import android.widget.ImageView @@ -10,8 +9,6 @@ import com.casic.endoscope.adapter.MediaDirAdapter import com.casic.endoscope.databinding.ActivityAlbumBinding import com.casic.endoscope.utils.FileManager -import com.casic.endoscope.utils.ProjectConstant -import com.casic.endoscope.widgets.AlertControlDialog import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.adapter.ViewHolder import com.pengxh.kt.lite.base.KotlinBaseActivity @@ -19,15 +16,12 @@ import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getStatusBarHeight import com.pengxh.kt.lite.extensions.navigatePageTo -import com.pengxh.kt.lite.extensions.show import com.shuyu.gsyvideoplayer.GSYVideoManager import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File -import java.util.Timer -import java.util.TimerTask class AlbumActivity : KotlinBaseActivity() { @@ -39,74 +33,7 @@ private lateinit var dirBeans: MutableList private lateinit var directoryAdapter: MediaDirAdapter - private var timer: Timer? = null - - @SuppressLint("NotifyDataSetChanged") override fun initEvent() { - binding.videoDecodeButton.setOnClickListener { - val originVideoCollection = ArrayList() - val convertedVideoCollection = ArrayList() - val videoDir = File(FileManager.getRootDirectory(), "Video") - videoDir.listFiles()?.forEach { dir -> - val childDir = File(dir.absolutePath) - childDir.listFiles()?.forEach { file -> - if (file.name.startsWith("t")) { - convertedVideoCollection.add(file.absolutePath) - } else { - originVideoCollection.add(file.absolutePath) - } - } - } - - //将已转换的全部去掉t,得到一个临时数组,在和原数组比较,得到差集 - val temp = ArrayList() - convertedVideoCollection.forEach { - temp.add(it.replace("/t", "/")) - } - - //将temp和originVideoCollection求差集 - val stringSet = originVideoCollection.subtract(temp.toSet()) - if (stringSet.isEmpty()) { - "没有需要手动转码的视频".show(context) - return@setOnClickListener - } - AlertControlDialog.Builder() - .setContext(context) - .setTitle("温馨提示") - .setMessage("共有${stringSet.size}个视频需要转码,是否继续?") - .setNegativeButton("取消") - .setPositiveButton("确定") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - binding.progressBarLayout.visibility = View.VISIBLE - binding.progressBar.max = stringSet.size - stringSet.forEach { - ProjectConstant.VIDEO_PATH_STACK.push(it) - } - - timer = Timer() - timer?.schedule(object : TimerTask() { - override fun run() { - runOnUiThread { - binding.progressBar.progress = ProjectConstant.decodedViewCount - if (ProjectConstant.decodedViewCount == stringSet.size) { - binding.progressBarLayout.visibility = View.INVISIBLE - timer?.cancel() - } - //刷新列表 - directoryAdapter.notifyDataSetChanged() - } - } - }, 0, 2000) - } - - override fun onCancelClick() { - - } - }).build().show() - } - binding.ascButton.setOnClickListener { //按时间排序,降序 dirBeans = tempSet.sortedWith { d1, d2 -> @@ -125,10 +52,6 @@ } override fun initOnCreate(savedInstanceState: Bundle?) { - //初始化已转码得视频数量和状态 - ProjectConstant.isUnderDecodingVideo = false - ProjectConstant.decodedViewCount = 0 - val temp = ArrayList() val videoDir = File(FileManager.getRootDirectory(), "Video") videoDir.list()?.forEach { diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt new file mode 100644 index 0000000..765db08 --- /dev/null +++ b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt @@ -0,0 +1,136 @@ +package com.casic.endoscope.service + +import android.app.Activity +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.MediaRecorder +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import com.casic.endoscope.R +import com.pengxh.kt.lite.extensions.getSystemService +import kotlinx.coroutines.Runnable + +class ScreenShortRecordService : Service() { + + private val kTag = "ScreenShortRecordService" + private var mediaProjection: MediaProjection? = null + private var recorder: MediaRecorder? = null + private var virtualDisplay: VirtualDisplay? = null + + inner class ScreenShortRecordServiceBinder : Binder() { + fun getScreenShortRecordService(): ScreenShortRecordService { + return this@ScreenShortRecordService + } + } + + override fun onBind(intent: Intent?): IBinder { + return ScreenShortRecordServiceBinder() + } + + fun startRecorder(videoPath: String, intent: Intent) { + Log.d(kTag, "startRecorder: 开始录屏 $videoPath") + //开启通知,并申请成为前台服务 + createForegroundNotification() + + val mediaProjectionManager = getSystemService() + //获得令牌 + mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, intent) + Handler(Looper.myLooper()!!).postDelayed(Runnable { + //配置MediaRecorder + if (configMediaRecorder(videoPath)) { + try { + //开始录屏 + recorder?.start() + } catch (e: Exception) { + e.printStackTrace() + } + } + }, 400) + } + + private fun createForegroundNotification() { + val notificationManager = getSystemService() + val builder: Notification.Builder + val name = resources.getString(R.string.app_name) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //创建渠道 + val id = "${kTag}Channel" + val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) + channel.setShowBadge(true) + channel.enableVibration(false) + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 + notificationManager?.createNotificationChannel(channel) + builder = Notification.Builder(this, id) + } else { + builder = Notification.Builder(this) + } + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + builder.setContentTitle(name) + .setContentText("${name}录屏中") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(bitmap) + val notification = builder.build() + notification.flags = Notification.FLAG_NO_CLEAR + startForeground(Int.MAX_VALUE, notification) + } + + private fun configMediaRecorder(videoPath: String): Boolean { + val dm = resources.displayMetrics + recorder = MediaRecorder() + recorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) //音频载体 + setVideoSource(MediaRecorder.VideoSource.SURFACE) //视频载体 + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) //输出格式 + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //音频格式 + setVideoEncoder(MediaRecorder.VideoEncoder.H264) //视频格式 + setVideoSize(dm.widthPixels, dm.heightPixels) //size + setVideoFrameRate(30) //帧率 + setVideoEncodingBitRate(3 * 1024 * 1024) //比特率 + //设置文件位置 + setOutputFile(videoPath) + try { + prepare() + virtualDisplay = mediaProjection?.createVirtualDisplay( + kTag, + dm.widthPixels, + dm.heightPixels, + dm.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, + null + ) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return true + } + + //停止录制 + fun stopRecorder() { + recorder?.stop() + recorder?.release() + recorder = null + + mediaProjection?.stop() + + //退出前台服务 + stopForeground(true) + Log.d(kTag, "stopRecorder: 结束录屏") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt b/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt deleted file mode 100644 index f54c2a2..0000000 --- a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.casic.endoscope.service - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import com.casic.endoscope.extensions.transcodeVideo -import com.casic.endoscope.utils.ProjectConstant -import java.util.Timer -import java.util.TimerTask - -class VideoTranscodeService : Service() { - - private val kTag = "VideoTranscodeService" - private var timer: Timer? = null - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - timer = Timer() - Log.d(kTag, "onCreate => $kTag") - } - - - /** - * 执行该方法后,Service 会启动并在后台无限期执行 - * 需要调用 stopSelf() 或 stopService() 来结束Service - * */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - //定时遍历缓存,看是否有新的视频被录制 - timer?.schedule(object : TimerTask() { - override fun run() { - //开始后台转码 - if (!ProjectConstant.isUnderDecodingVideo && ProjectConstant.VIDEO_PATH_STACK.isNotEmpty()) { - ProjectConstant.isUnderDecodingVideo = true - ProjectConstant.VIDEO_PATH_STACK.pop().transcodeVideo(kTag) - } else { - Log.d(kTag, "run: 转码中......") - } - } - }, 0, 1000) - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - Log.d(kTag, "onDestroy => $kTag") - timer?.cancel() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt b/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt deleted file mode 100644 index 128fbd8..0000000 --- a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.endoscope.utils - -import io.microshow.rxffmpeg.RxFFmpegCommandList - -object FFmpegCommandHub { - - /** - * 海康视频转码指令 - * */ - fun createVideoTranscodeCommand(inputFilePath: String, outputFilePath: String): Array { - val commandParams = RxFFmpegCommandList() - commandParams.append("-i") - commandParams.append(inputFilePath) - commandParams.append("-c:v") - commandParams.append("libx264") - commandParams.append(outputFilePath) - return commandParams.build() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt index 02e57c2..78c2ac0 100644 --- a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt +++ b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt @@ -2,7 +2,6 @@ import android.Manifest import android.os.Build -import java.util.Stack object ProjectConstant { val USER_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -14,7 +13,8 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( @@ -23,21 +23,24 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } @@ -49,14 +52,5 @@ const val HK_NET_USERNAME = "admin" const val HK_NET_PASSWORD = "casic203" - //待转码视频路径 - val VIDEO_PATH_STACK = Stack() - const val SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb" //连接设备的UUID - - //视频正在转码 - var isUnderDecodingVideo = false - - //已转码得视频数量 - var decodedViewCount = 0 } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt b/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt index b4e6474..692f453 100644 --- a/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt +++ b/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt @@ -1,6 +1,5 @@ package com.casic.endoscope.view -import android.annotation.SuppressLint import android.os.Bundle import android.view.View import android.widget.ImageView @@ -10,8 +9,6 @@ import com.casic.endoscope.adapter.MediaDirAdapter import com.casic.endoscope.databinding.ActivityAlbumBinding import com.casic.endoscope.utils.FileManager -import com.casic.endoscope.utils.ProjectConstant -import com.casic.endoscope.widgets.AlertControlDialog import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.adapter.ViewHolder import com.pengxh.kt.lite.base.KotlinBaseActivity @@ -19,15 +16,12 @@ import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getStatusBarHeight import com.pengxh.kt.lite.extensions.navigatePageTo -import com.pengxh.kt.lite.extensions.show import com.shuyu.gsyvideoplayer.GSYVideoManager import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File -import java.util.Timer -import java.util.TimerTask class AlbumActivity : KotlinBaseActivity() { @@ -39,74 +33,7 @@ private lateinit var dirBeans: MutableList private lateinit var directoryAdapter: MediaDirAdapter - private var timer: Timer? = null - - @SuppressLint("NotifyDataSetChanged") override fun initEvent() { - binding.videoDecodeButton.setOnClickListener { - val originVideoCollection = ArrayList() - val convertedVideoCollection = ArrayList() - val videoDir = File(FileManager.getRootDirectory(), "Video") - videoDir.listFiles()?.forEach { dir -> - val childDir = File(dir.absolutePath) - childDir.listFiles()?.forEach { file -> - if (file.name.startsWith("t")) { - convertedVideoCollection.add(file.absolutePath) - } else { - originVideoCollection.add(file.absolutePath) - } - } - } - - //将已转换的全部去掉t,得到一个临时数组,在和原数组比较,得到差集 - val temp = ArrayList() - convertedVideoCollection.forEach { - temp.add(it.replace("/t", "/")) - } - - //将temp和originVideoCollection求差集 - val stringSet = originVideoCollection.subtract(temp.toSet()) - if (stringSet.isEmpty()) { - "没有需要手动转码的视频".show(context) - return@setOnClickListener - } - AlertControlDialog.Builder() - .setContext(context) - .setTitle("温馨提示") - .setMessage("共有${stringSet.size}个视频需要转码,是否继续?") - .setNegativeButton("取消") - .setPositiveButton("确定") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - binding.progressBarLayout.visibility = View.VISIBLE - binding.progressBar.max = stringSet.size - stringSet.forEach { - ProjectConstant.VIDEO_PATH_STACK.push(it) - } - - timer = Timer() - timer?.schedule(object : TimerTask() { - override fun run() { - runOnUiThread { - binding.progressBar.progress = ProjectConstant.decodedViewCount - if (ProjectConstant.decodedViewCount == stringSet.size) { - binding.progressBarLayout.visibility = View.INVISIBLE - timer?.cancel() - } - //刷新列表 - directoryAdapter.notifyDataSetChanged() - } - } - }, 0, 2000) - } - - override fun onCancelClick() { - - } - }).build().show() - } - binding.ascButton.setOnClickListener { //按时间排序,降序 dirBeans = tempSet.sortedWith { d1, d2 -> @@ -125,10 +52,6 @@ } override fun initOnCreate(savedInstanceState: Bundle?) { - //初始化已转码得视频数量和状态 - ProjectConstant.isUnderDecodingVideo = false - ProjectConstant.decodedViewCount = 0 - val temp = ArrayList() val videoDir = File(FileManager.getRootDirectory(), "Video") videoDir.list()?.forEach { diff --git a/app/src/main/java/com/casic/endoscope/view/MainActivity.kt b/app/src/main/java/com/casic/endoscope/view/MainActivity.kt index cddc6fb..768bffb 100644 --- a/app/src/main/java/com/casic/endoscope/view/MainActivity.kt +++ b/app/src/main/java/com/casic/endoscope/view/MainActivity.kt @@ -1,16 +1,22 @@ package com.casic.endoscope.view import android.annotation.SuppressLint +import android.app.Activity import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothGatt +import android.content.ComponentName +import android.content.Context import android.content.Intent +import android.content.ServiceConnection import android.graphics.Color import android.graphics.PixelFormat +import android.media.projection.MediaProjectionManager import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment import android.os.Handler +import android.os.IBinder import android.os.Message import android.provider.Settings import android.util.Log @@ -28,7 +34,7 @@ import com.casic.endoscope.extensions.getChannel import com.casic.endoscope.extensions.init import com.casic.endoscope.extensions.toTime -import com.casic.endoscope.service.VideoTranscodeService +import com.casic.endoscope.service.ScreenShortRecordService import com.casic.endoscope.utils.DataBaseManager import com.casic.endoscope.utils.FileManager import com.casic.endoscope.utils.ProjectConstant @@ -57,6 +63,7 @@ import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getStatusBarHeight +import com.pengxh.kt.lite.extensions.getSystemService import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.extensions.timestampToTime @@ -119,12 +126,12 @@ private var timer: Timer? = null private var timerTask: TimerTask? = null private var seconds = 0L - private var videoPath = "" + private var screenShortService: ScreenShortRecordService? = null private lateinit var weakReferenceHandler: WeakReferenceHandler - private lateinit var serviceIntent: Intent private lateinit var dataSet: LineDataSet private lateinit var lineData: LineData private lateinit var imageAdapter: NormalRecyclerAdapter + private lateinit var mediaProjectionManager: MediaProjectionManager override fun initViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) @@ -137,6 +144,13 @@ FileManager.getRootDirectory() } } + + if (requestCode == 2 && resultCode == Activity.RESULT_OK) { + val videoPath = "/${FileManager.getVideoFileDir()}/${timeFormat.format(Date())}.mp4" + data?.let { + screenShortService?.startRecorder(videoPath, it) + } + } } override fun initOnCreate(savedInstanceState: Bundle?) { @@ -154,8 +168,6 @@ weakReferenceHandler = WeakReferenceHandler(this) weakReferenceHandler.sendEmptyMessage(messageCode) - serviceIntent = Intent(this, VideoTranscodeService::class.java) - //初始化浓度趋势折线图和Marker binding.lineChart.init(this) val markerView = LineChartMarkerView(this) @@ -164,12 +176,6 @@ binding.lineChart.marker = markerView } - override fun onResume() { - super.onResume() - //启动视频转码服务 - startService(serviceIntent) - } - override fun initEvent() { binding.openAlbumButton.setOnClickListener { if (isPreviewSuccess) { @@ -315,8 +321,7 @@ val deviceInfo = SDKGuider.sdkGuider.devManageGuider.devList[0] returnUserId = deviceInfo.szUserId - aChannelNum = - deviceInfo.deviceInfoV40_jna.struDeviceV30.byChanNum.toInt() + aChannelNum = deviceInfo.deviceInfoV40_jna.struDeviceV30.byChanNum.toInt() startAChannel = deviceInfo.deviceInfoV40_jna.struDeviceV30.byStartChan.toInt() @@ -442,20 +447,20 @@ //视频录制 binding.videoButton.setOnClickListener { - if (isPreviewSuccess) { - videoPath = "/${FileManager.getVideoFileDir()}/${timeFormat.format(Date())}.mp4" - hkSDK.NET_DVR_SaveRealData(returnUserId, videoPath) - binding.videoStateView.visibility = View.VISIBLE + mediaProjectionManager = + getSystemService() ?: return@setOnClickListener + val captureIntent = mediaProjectionManager.createScreenCaptureIntent() + if (captureIntent.resolveActivity(packageManager) != null) { + startActivityForResult(captureIntent, 2) } else { - "摄像头预览未打开,无法录制视频".show(this) + "该设备不支持录屏".show(this) } } + //视频保存 binding.videoButton.setOnLongClickListener { - //停止视频抓取 - hkSDK.NET_DVR_StopSaveRealData(returnUserId) + screenShortService?.stopRecorder() binding.videoStateView.visibility = View.INVISIBLE - ProjectConstant.VIDEO_PATH_STACK.push(videoPath) "视频录制成功".show(this) true } @@ -467,8 +472,8 @@ serialStart.dwSerialPort = 2 //串口编号 serialStart.wPort = 0 - val serialHandle = hkSDK.NET_DVR_SerialStart_V40(returnUserId, serialStart) - { _, _, _, _ -> } + val serialHandle = + hkSDK.NET_DVR_SerialStart_V40(returnUserId, serialStart) { _, _, _, _ -> } //链式执行 lifecycleScope.launch(Dispatchers.IO) { flow { @@ -505,6 +510,31 @@ } } + override fun onStart() { + super.onStart() + Intent(this, ScreenShortRecordService::class.java).also { intent -> + bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) + } + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is ScreenShortRecordService.ScreenShortRecordServiceBinder) { + //截屏 + screenShortService = iBinder.getScreenShortRecordService() + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + "录屏服务已断开".show(context) + } + } + + override fun onStop() { + super.onStop() + unbindService(serviceConnection) + } + override fun handleMessage(msg: Message): Boolean { if (msg.what == messageCode) { //绑定数据 @@ -514,13 +544,9 @@ override fun convertView(viewHolder: ViewHolder, position: Int, item: String) { viewHolder.setImageResource(R.id.imageView, item) viewHolder.setOnLongClickListener(R.id.imageView) { - AlertControlDialog.Builder() - .setContext(context) - .setTitle("温馨提示") - .setMessage("是否保存此图片?") - .setNegativeButton("取消") - .setPositiveButton("确定") - .setOnDialogButtonClickListener(object : + AlertControlDialog.Builder().setContext(context).setTitle("温馨提示") + .setMessage("是否保存此图片?").setNegativeButton("取消") + .setPositiveButton("确定").setOnDialogButtonClickListener(object : AlertControlDialog.OnDialogButtonClickListener { override fun onConfirmClick() { @@ -538,10 +564,7 @@ viewHolder.setOnClickListener(R.id.imageView) { //查看大图Dialog - PreviewImageDialog.Builder() - .setContext(context) - .setImagePath(item) - .build() + PreviewImageDialog.Builder().setContext(context).setImagePath(item).build() .show() } } @@ -630,7 +653,6 @@ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { if (System.currentTimeMillis() - clickTime > 2000) { - stopService(serviceIntent) "再按一次退出应用".show(this) clickTime = System.currentTimeMillis() return true diff --git a/app/build.gradle b/app/build.gradle index d518570..9b25206 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //基础依赖库 - implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.9' + implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10' implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.cardview:cardview:1.0.0' @@ -111,6 +111,4 @@ implementation 'org.greenrobot:greendao:3.3.0' //数据库升级 implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1' - //ffmpeg - implementation 'com.github.microshow:RxFFmpeg:4.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 85791a0..d9e496a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,10 @@ + + + + + + android:name=".service.ScreenShortRecordService" + android:enabled="true" + android:exported="true" + android:foregroundServiceType="mediaProjection" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt index 23a553d..fa7ec43 100644 --- a/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt +++ b/app/src/main/java/com/casic/endoscope/adapter/MediaDirAdapter.kt @@ -42,9 +42,7 @@ val fileBeans = ArrayList() val videoDir = File(File(FileManager.getRootDirectory(), "Video"), date) videoDir.listFiles()?.forEach { - if (it.name.startsWith("t")) { - fileBeans.add(it) - } + fileBeans.add(it) } val imageDir = File(File(FileManager.getRootDirectory(), "Picture"), date) diff --git a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt index 21cc4f5..6439217 100644 --- a/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/endoscope/base/BaseApplication.kt @@ -5,7 +5,6 @@ import com.casic.endoscope.greendao.DaoSession import com.casic.endoscope.utils.EndoscopeDevOpenHelper import com.pengxh.kt.lite.utils.SaveKeyValues -import io.microshow.rxffmpeg.RxFFmpegInvoke import kotlin.properties.Delegates class BaseApplication : Application() { @@ -25,7 +24,6 @@ application = this SaveKeyValues.initSharedPreferences(this) initDataBase() - RxFFmpegInvoke.getInstance().setDebug(false) } private fun initDataBase() { diff --git a/app/src/main/java/com/casic/endoscope/extensions/String.kt b/app/src/main/java/com/casic/endoscope/extensions/String.kt index 2762fe7..7e5bdc6 100644 --- a/app/src/main/java/com/casic/endoscope/extensions/String.kt +++ b/app/src/main/java/com/casic/endoscope/extensions/String.kt @@ -1,10 +1,5 @@ package com.casic.endoscope.extensions -import android.util.Log -import com.casic.endoscope.utils.FFmpegCommandHub -import com.casic.endoscope.utils.ProjectConstant -import io.microshow.rxffmpeg.RxFFmpegInvoke -import io.microshow.rxffmpeg.RxFFmpegSubscriber import java.util.regex.Pattern /** @@ -15,36 +10,4 @@ val p = Pattern.compile(regEx) val m = p.matcher(this) return m.replaceAll("").trim { it <= ' ' } -} - -fun String.transcodeVideo(kTag: String) { - /** - * //storage/emulated/0/Android/data/com.casic.endoscope/files/Movies/2024-02-21/20240221161555.mp4 - * */ - val lastIndex = this.lastIndexOf("/") - val fileName = this.drop(lastIndex + 1) - //文件名前面+t - val newFileName = fileName.reversed().plus("t").reversed() - val outputPath = this.replace(fileName, newFileName) - RxFFmpegInvoke.getInstance() - .runCommandRxJava(FFmpegCommandHub.createVideoTranscodeCommand(this, outputPath)) - .subscribe(object : RxFFmpegSubscriber() { - override fun onError(message: String?) { - - } - - override fun onFinish() { - Log.d(kTag, "onFinish => $outputPath 转码完成") - ProjectConstant.decodedViewCount += 1 - ProjectConstant.isUnderDecodingVideo = false - } - - override fun onProgress(progress: Int, progressTime: Long) { - Log.d(kTag, "onProgress => $progress") - } - - override fun onCancel() { - - } - }) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt new file mode 100644 index 0000000..765db08 --- /dev/null +++ b/app/src/main/java/com/casic/endoscope/service/ScreenShortRecordService.kt @@ -0,0 +1,136 @@ +package com.casic.endoscope.service + +import android.app.Activity +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.media.MediaRecorder +import android.media.projection.MediaProjection +import android.media.projection.MediaProjectionManager +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import com.casic.endoscope.R +import com.pengxh.kt.lite.extensions.getSystemService +import kotlinx.coroutines.Runnable + +class ScreenShortRecordService : Service() { + + private val kTag = "ScreenShortRecordService" + private var mediaProjection: MediaProjection? = null + private var recorder: MediaRecorder? = null + private var virtualDisplay: VirtualDisplay? = null + + inner class ScreenShortRecordServiceBinder : Binder() { + fun getScreenShortRecordService(): ScreenShortRecordService { + return this@ScreenShortRecordService + } + } + + override fun onBind(intent: Intent?): IBinder { + return ScreenShortRecordServiceBinder() + } + + fun startRecorder(videoPath: String, intent: Intent) { + Log.d(kTag, "startRecorder: 开始录屏 $videoPath") + //开启通知,并申请成为前台服务 + createForegroundNotification() + + val mediaProjectionManager = getSystemService() + //获得令牌 + mediaProjection = mediaProjectionManager?.getMediaProjection(Activity.RESULT_OK, intent) + Handler(Looper.myLooper()!!).postDelayed(Runnable { + //配置MediaRecorder + if (configMediaRecorder(videoPath)) { + try { + //开始录屏 + recorder?.start() + } catch (e: Exception) { + e.printStackTrace() + } + } + }, 400) + } + + private fun createForegroundNotification() { + val notificationManager = getSystemService() + val builder: Notification.Builder + val name = resources.getString(R.string.app_name) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //创建渠道 + val id = "${kTag}Channel" + val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) + channel.setShowBadge(true) + channel.enableVibration(false) + channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC //设置锁屏可见 + notificationManager?.createNotificationChannel(channel) + builder = Notification.Builder(this, id) + } else { + builder = Notification.Builder(this) + } + val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) + builder.setContentTitle(name) + .setContentText("${name}录屏中") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.mipmap.ic_launcher) + .setLargeIcon(bitmap) + val notification = builder.build() + notification.flags = Notification.FLAG_NO_CLEAR + startForeground(Int.MAX_VALUE, notification) + } + + private fun configMediaRecorder(videoPath: String): Boolean { + val dm = resources.displayMetrics + recorder = MediaRecorder() + recorder?.apply { + setAudioSource(MediaRecorder.AudioSource.MIC) //音频载体 + setVideoSource(MediaRecorder.VideoSource.SURFACE) //视频载体 + setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) //输出格式 + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //音频格式 + setVideoEncoder(MediaRecorder.VideoEncoder.H264) //视频格式 + setVideoSize(dm.widthPixels, dm.heightPixels) //size + setVideoFrameRate(30) //帧率 + setVideoEncodingBitRate(3 * 1024 * 1024) //比特率 + //设置文件位置 + setOutputFile(videoPath) + try { + prepare() + virtualDisplay = mediaProjection?.createVirtualDisplay( + kTag, + dm.widthPixels, + dm.heightPixels, + dm.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + surface, + null, + null + ) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return true + } + + //停止录制 + fun stopRecorder() { + recorder?.stop() + recorder?.release() + recorder = null + + mediaProjection?.stop() + + //退出前台服务 + stopForeground(true) + Log.d(kTag, "stopRecorder: 结束录屏") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt b/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt deleted file mode 100644 index f54c2a2..0000000 --- a/app/src/main/java/com/casic/endoscope/service/VideoTranscodeService.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.casic.endoscope.service - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import com.casic.endoscope.extensions.transcodeVideo -import com.casic.endoscope.utils.ProjectConstant -import java.util.Timer -import java.util.TimerTask - -class VideoTranscodeService : Service() { - - private val kTag = "VideoTranscodeService" - private var timer: Timer? = null - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - timer = Timer() - Log.d(kTag, "onCreate => $kTag") - } - - - /** - * 执行该方法后,Service 会启动并在后台无限期执行 - * 需要调用 stopSelf() 或 stopService() 来结束Service - * */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - //定时遍历缓存,看是否有新的视频被录制 - timer?.schedule(object : TimerTask() { - override fun run() { - //开始后台转码 - if (!ProjectConstant.isUnderDecodingVideo && ProjectConstant.VIDEO_PATH_STACK.isNotEmpty()) { - ProjectConstant.isUnderDecodingVideo = true - ProjectConstant.VIDEO_PATH_STACK.pop().transcodeVideo(kTag) - } else { - Log.d(kTag, "run: 转码中......") - } - } - }, 0, 1000) - return START_NOT_STICKY - } - - override fun onDestroy() { - super.onDestroy() - Log.d(kTag, "onDestroy => $kTag") - timer?.cancel() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt b/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt deleted file mode 100644 index 128fbd8..0000000 --- a/app/src/main/java/com/casic/endoscope/utils/FFmpegCommandHub.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.endoscope.utils - -import io.microshow.rxffmpeg.RxFFmpegCommandList - -object FFmpegCommandHub { - - /** - * 海康视频转码指令 - * */ - fun createVideoTranscodeCommand(inputFilePath: String, outputFilePath: String): Array { - val commandParams = RxFFmpegCommandList() - commandParams.append("-i") - commandParams.append(inputFilePath) - commandParams.append("-c:v") - commandParams.append("libx264") - commandParams.append(outputFilePath) - return commandParams.build() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt index 02e57c2..78c2ac0 100644 --- a/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt +++ b/app/src/main/java/com/casic/endoscope/utils/ProjectConstant.kt @@ -2,7 +2,6 @@ import android.Manifest import android.os.Build -import java.util.Stack object ProjectConstant { val USER_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -14,7 +13,8 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( @@ -23,21 +23,24 @@ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } else { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.RECORD_AUDIO ) } @@ -49,14 +52,5 @@ const val HK_NET_USERNAME = "admin" const val HK_NET_PASSWORD = "casic203" - //待转码视频路径 - val VIDEO_PATH_STACK = Stack() - const val SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb" //连接设备的UUID - - //视频正在转码 - var isUnderDecodingVideo = false - - //已转码得视频数量 - var decodedViewCount = 0 } \ No newline at end of file diff --git a/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt b/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt index b4e6474..692f453 100644 --- a/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt +++ b/app/src/main/java/com/casic/endoscope/view/AlbumActivity.kt @@ -1,6 +1,5 @@ package com.casic.endoscope.view -import android.annotation.SuppressLint import android.os.Bundle import android.view.View import android.widget.ImageView @@ -10,8 +9,6 @@ import com.casic.endoscope.adapter.MediaDirAdapter import com.casic.endoscope.databinding.ActivityAlbumBinding import com.casic.endoscope.utils.FileManager -import com.casic.endoscope.utils.ProjectConstant -import com.casic.endoscope.widgets.AlertControlDialog import com.gyf.immersionbar.ImmersionBar import com.pengxh.kt.lite.adapter.ViewHolder import com.pengxh.kt.lite.base.KotlinBaseActivity @@ -19,15 +16,12 @@ import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getStatusBarHeight import com.pengxh.kt.lite.extensions.navigatePageTo -import com.pengxh.kt.lite.extensions.show import com.shuyu.gsyvideoplayer.GSYVideoManager import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File -import java.util.Timer -import java.util.TimerTask class AlbumActivity : KotlinBaseActivity() { @@ -39,74 +33,7 @@ private lateinit var dirBeans: MutableList private lateinit var directoryAdapter: MediaDirAdapter - private var timer: Timer? = null - - @SuppressLint("NotifyDataSetChanged") override fun initEvent() { - binding.videoDecodeButton.setOnClickListener { - val originVideoCollection = ArrayList() - val convertedVideoCollection = ArrayList() - val videoDir = File(FileManager.getRootDirectory(), "Video") - videoDir.listFiles()?.forEach { dir -> - val childDir = File(dir.absolutePath) - childDir.listFiles()?.forEach { file -> - if (file.name.startsWith("t")) { - convertedVideoCollection.add(file.absolutePath) - } else { - originVideoCollection.add(file.absolutePath) - } - } - } - - //将已转换的全部去掉t,得到一个临时数组,在和原数组比较,得到差集 - val temp = ArrayList() - convertedVideoCollection.forEach { - temp.add(it.replace("/t", "/")) - } - - //将temp和originVideoCollection求差集 - val stringSet = originVideoCollection.subtract(temp.toSet()) - if (stringSet.isEmpty()) { - "没有需要手动转码的视频".show(context) - return@setOnClickListener - } - AlertControlDialog.Builder() - .setContext(context) - .setTitle("温馨提示") - .setMessage("共有${stringSet.size}个视频需要转码,是否继续?") - .setNegativeButton("取消") - .setPositiveButton("确定") - .setOnDialogButtonClickListener(object : - AlertControlDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - binding.progressBarLayout.visibility = View.VISIBLE - binding.progressBar.max = stringSet.size - stringSet.forEach { - ProjectConstant.VIDEO_PATH_STACK.push(it) - } - - timer = Timer() - timer?.schedule(object : TimerTask() { - override fun run() { - runOnUiThread { - binding.progressBar.progress = ProjectConstant.decodedViewCount - if (ProjectConstant.decodedViewCount == stringSet.size) { - binding.progressBarLayout.visibility = View.INVISIBLE - timer?.cancel() - } - //刷新列表 - directoryAdapter.notifyDataSetChanged() - } - } - }, 0, 2000) - } - - override fun onCancelClick() { - - } - }).build().show() - } - binding.ascButton.setOnClickListener { //按时间排序,降序 dirBeans = tempSet.sortedWith { d1, d2 -> @@ -125,10 +52,6 @@ } override fun initOnCreate(savedInstanceState: Bundle?) { - //初始化已转码得视频数量和状态 - ProjectConstant.isUnderDecodingVideo = false - ProjectConstant.decodedViewCount = 0 - val temp = ArrayList() val videoDir = File(FileManager.getRootDirectory(), "Video") videoDir.list()?.forEach { diff --git a/app/src/main/java/com/casic/endoscope/view/MainActivity.kt b/app/src/main/java/com/casic/endoscope/view/MainActivity.kt index cddc6fb..768bffb 100644 --- a/app/src/main/java/com/casic/endoscope/view/MainActivity.kt +++ b/app/src/main/java/com/casic/endoscope/view/MainActivity.kt @@ -1,16 +1,22 @@ package com.casic.endoscope.view import android.annotation.SuppressLint +import android.app.Activity import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothGatt +import android.content.ComponentName +import android.content.Context import android.content.Intent +import android.content.ServiceConnection import android.graphics.Color import android.graphics.PixelFormat +import android.media.projection.MediaProjectionManager import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment import android.os.Handler +import android.os.IBinder import android.os.Message import android.provider.Settings import android.util.Log @@ -28,7 +34,7 @@ import com.casic.endoscope.extensions.getChannel import com.casic.endoscope.extensions.init import com.casic.endoscope.extensions.toTime -import com.casic.endoscope.service.VideoTranscodeService +import com.casic.endoscope.service.ScreenShortRecordService import com.casic.endoscope.utils.DataBaseManager import com.casic.endoscope.utils.FileManager import com.casic.endoscope.utils.ProjectConstant @@ -57,6 +63,7 @@ import com.pengxh.kt.lite.divider.RecyclerViewItemOffsets import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getStatusBarHeight +import com.pengxh.kt.lite.extensions.getSystemService import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.extensions.timestampToTime @@ -119,12 +126,12 @@ private var timer: Timer? = null private var timerTask: TimerTask? = null private var seconds = 0L - private var videoPath = "" + private var screenShortService: ScreenShortRecordService? = null private lateinit var weakReferenceHandler: WeakReferenceHandler - private lateinit var serviceIntent: Intent private lateinit var dataSet: LineDataSet private lateinit var lineData: LineData private lateinit var imageAdapter: NormalRecyclerAdapter + private lateinit var mediaProjectionManager: MediaProjectionManager override fun initViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) @@ -137,6 +144,13 @@ FileManager.getRootDirectory() } } + + if (requestCode == 2 && resultCode == Activity.RESULT_OK) { + val videoPath = "/${FileManager.getVideoFileDir()}/${timeFormat.format(Date())}.mp4" + data?.let { + screenShortService?.startRecorder(videoPath, it) + } + } } override fun initOnCreate(savedInstanceState: Bundle?) { @@ -154,8 +168,6 @@ weakReferenceHandler = WeakReferenceHandler(this) weakReferenceHandler.sendEmptyMessage(messageCode) - serviceIntent = Intent(this, VideoTranscodeService::class.java) - //初始化浓度趋势折线图和Marker binding.lineChart.init(this) val markerView = LineChartMarkerView(this) @@ -164,12 +176,6 @@ binding.lineChart.marker = markerView } - override fun onResume() { - super.onResume() - //启动视频转码服务 - startService(serviceIntent) - } - override fun initEvent() { binding.openAlbumButton.setOnClickListener { if (isPreviewSuccess) { @@ -315,8 +321,7 @@ val deviceInfo = SDKGuider.sdkGuider.devManageGuider.devList[0] returnUserId = deviceInfo.szUserId - aChannelNum = - deviceInfo.deviceInfoV40_jna.struDeviceV30.byChanNum.toInt() + aChannelNum = deviceInfo.deviceInfoV40_jna.struDeviceV30.byChanNum.toInt() startAChannel = deviceInfo.deviceInfoV40_jna.struDeviceV30.byStartChan.toInt() @@ -442,20 +447,20 @@ //视频录制 binding.videoButton.setOnClickListener { - if (isPreviewSuccess) { - videoPath = "/${FileManager.getVideoFileDir()}/${timeFormat.format(Date())}.mp4" - hkSDK.NET_DVR_SaveRealData(returnUserId, videoPath) - binding.videoStateView.visibility = View.VISIBLE + mediaProjectionManager = + getSystemService() ?: return@setOnClickListener + val captureIntent = mediaProjectionManager.createScreenCaptureIntent() + if (captureIntent.resolveActivity(packageManager) != null) { + startActivityForResult(captureIntent, 2) } else { - "摄像头预览未打开,无法录制视频".show(this) + "该设备不支持录屏".show(this) } } + //视频保存 binding.videoButton.setOnLongClickListener { - //停止视频抓取 - hkSDK.NET_DVR_StopSaveRealData(returnUserId) + screenShortService?.stopRecorder() binding.videoStateView.visibility = View.INVISIBLE - ProjectConstant.VIDEO_PATH_STACK.push(videoPath) "视频录制成功".show(this) true } @@ -467,8 +472,8 @@ serialStart.dwSerialPort = 2 //串口编号 serialStart.wPort = 0 - val serialHandle = hkSDK.NET_DVR_SerialStart_V40(returnUserId, serialStart) - { _, _, _, _ -> } + val serialHandle = + hkSDK.NET_DVR_SerialStart_V40(returnUserId, serialStart) { _, _, _, _ -> } //链式执行 lifecycleScope.launch(Dispatchers.IO) { flow { @@ -505,6 +510,31 @@ } } + override fun onStart() { + super.onStart() + Intent(this, ScreenShortRecordService::class.java).also { intent -> + bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) + } + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is ScreenShortRecordService.ScreenShortRecordServiceBinder) { + //截屏 + screenShortService = iBinder.getScreenShortRecordService() + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + "录屏服务已断开".show(context) + } + } + + override fun onStop() { + super.onStop() + unbindService(serviceConnection) + } + override fun handleMessage(msg: Message): Boolean { if (msg.what == messageCode) { //绑定数据 @@ -514,13 +544,9 @@ override fun convertView(viewHolder: ViewHolder, position: Int, item: String) { viewHolder.setImageResource(R.id.imageView, item) viewHolder.setOnLongClickListener(R.id.imageView) { - AlertControlDialog.Builder() - .setContext(context) - .setTitle("温馨提示") - .setMessage("是否保存此图片?") - .setNegativeButton("取消") - .setPositiveButton("确定") - .setOnDialogButtonClickListener(object : + AlertControlDialog.Builder().setContext(context).setTitle("温馨提示") + .setMessage("是否保存此图片?").setNegativeButton("取消") + .setPositiveButton("确定").setOnDialogButtonClickListener(object : AlertControlDialog.OnDialogButtonClickListener { override fun onConfirmClick() { @@ -538,10 +564,7 @@ viewHolder.setOnClickListener(R.id.imageView) { //查看大图Dialog - PreviewImageDialog.Builder() - .setContext(context) - .setImagePath(item) - .build() + PreviewImageDialog.Builder().setContext(context).setImagePath(item).build() .show() } } @@ -630,7 +653,6 @@ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { if (System.currentTimeMillis() - clickTime > 2000) { - stopService(serviceIntent) "再按一次退出应用".show(this) clickTime = System.currentTimeMillis() return true diff --git a/app/src/main/res/layout/activity_album.xml b/app/src/main/res/layout/activity_album.xml index 707e032..5c91bfb 100644 --- a/app/src/main/res/layout/activity_album.xml +++ b/app/src/main/res/layout/activity_album.xml @@ -55,14 +55,6 @@