diff --git a/app/RecordScreen.jks b/app/RecordScreen.jks new file mode 100644 index 0000000..8a25b7d Binary files /dev/null and b/app/RecordScreen.jks differ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ab92d13..aa659de 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,25 +1,30 @@ +import java.util.Date +import java.text.SimpleDateFormat plugins { id("com.android.application") id("org.jetbrains.kotlin.android") } - +val timestamp = SimpleDateFormat("MM_dd_HH_mm").format(Date()) android { namespace = "com.audio.record.screen.test" compileSdk = 35 defaultConfig { - applicationId = "com.audio.record.screen.test" + applicationId = "com.audio.record.screen" minSdk = 24 targetSdk = 35 versionCode = 1 versionName = "1.0" - + setProperty( + "archivesBaseName", + "RecordScreen_V" + versionName + "(${versionCode})_$timestamp" + ) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { - isMinifyEnabled = false + isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -79,8 +84,8 @@ dependencies { implementation("com.github.bumptech.glide:glide:4.16.0") - implementation(files("libs/jetified-ffmpeg-kit-full-6.0.aar")) - implementation(files("libs/smart-exception-common-0.2.1.jar")) - implementation(files("libs/smart-exception-java-0.2.1.jar")) +// implementation(files("libs/jetified-ffmpeg-kit-full-6.0.aar")) +// implementation(files("libs/smart-exception-common-0.2.1.jar")) +// implementation(files("libs/smart-exception-java-0.2.1.jar")) } \ No newline at end of file diff --git a/app/keystore.properties b/app/keystore.properties new file mode 100644 index 0000000..2ba649e --- /dev/null +++ b/app/keystore.properties @@ -0,0 +1,6 @@ +app_name=RecordScreen +package_name=com.audio.record.screen +keystoreFile=app/RecordScreen.jks +key_alias=RecordScreenkey0 +key_store_password=RecordScreen +key_password=RecordScreen \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 82e83c1..7020d3b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,10 +7,12 @@ android:required="false" /> - - - - + + + + + + + + + + + + + android:exported="true" + android:screenOrientation="portrait"> @@ -48,28 +58,16 @@ android:name=".activity.ImageViewActivity" android:exported="false" android:screenOrientation="portrait" /> - + - - - - - - + android:screenOrientation="portrait"/> + + - - - () { - val NOTIFICATION_PERMISSION_REQUEST_CODE = 123 - val SCREEN_CAPTURE_REQUEST_CODE = 124 - val REQUEST_SCREENSHOT = 125 - - lateinit var requestPermissionLauncher: ActivityResultLauncher - lateinit var micPermissionLauncher: ActivityResultLauncher - lateinit var cameraPermissionLauncher: ActivityResultLauncher - lateinit var requestStoragePermission: ActivityResultLauncher - - lateinit var mediaProjectionManager: MediaProjectionManager -// private var floatingService: ScreenRecordService? = null - - private lateinit var tmpVideoUri:Uri - -// private val connection = object : ServiceConnection { -// override fun onServiceConnected(name: ComponentName?, service: IBinder?) { -// val binder = service as ScreenRecordService.FloatingBinder -// floatingService = binder.getService() -// binder.setCallback(object : FloatingCallback { -// override fun onFloatingButtonClicked(action: String) { -// // 处理来自悬浮窗的点击 -// when (action) { -// "stop_clicked" -> { -// -// } -// } -// } -// }) -// } -// -// override fun onServiceDisconnected(name: ComponentName?) { -// floatingService = null -// } -// } - - - override fun initBinding(): ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater) - - override fun getFullColor(): Boolean? = true - - override fun onCreateInit() { - initPermissionLauncher() -// checkSyswindow(this@MainActivity) - checkStoragePermissionAndDoSomething() - - - binding.btn1.setOnClickListener { - requestNotification() - } - binding.btn2.setOnClickListener { - checkStoragePermissionAndDoSomething() - } - binding.btn3.setOnClickListener { - micPermissionLauncher.launch(android.Manifest.permission.RECORD_AUDIO) - } - binding.btn4.setOnClickListener { - checkCamera() - } - binding.btnShowcamera.setOnClickListener { - it.isSelected = !it.isSelected - if(it.isSelected){ - FloatingWindowBridge.sendCommand("show") - }else{ - FloatingWindowBridge.sendCommand("hide") - } - - } - binding.btn7.setOnClickListener { - startActivity(Intent(this,PreviewActivity::class.java)) - - } - - mediaProjectionManager = - getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager - binding.btn5.setOnClickListener { - startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), SCREEN_CAPTURE_REQUEST_CODE) - } - binding.btn6.setOnClickListener { - stopRecording() - } - - binding.btnScreenshot.setOnClickListener { - - startActivityForResult( mediaProjectionManager.createScreenCaptureIntent(), REQUEST_SCREENSHOT) - } - } - - override fun onInitPadding(): Boolean = true - - - private fun initPermissionLauncher() { - requestPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - if (isGranted) { - startForegroundService() - Common.showLog("权限授予") - } else { - Common.showLog("权限拒绝") - } - } - - micPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted -> - if (isGranted) { - Common.showLog("mic 权限授予") - } else { - Common.showLog("mic 权限拒绝") - } - } - cameraPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted -> - if (isGranted) { - Common.showLog("CAMERA 权限授予") - } else { - Common.showLog("CAMERA 权限拒绝") - } - } - requestStoragePermission = registerForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted -> - if (isGranted) { - // 权限已授予 - Common.showLog("已获取存储权限") - // 执行写入文件操作 - } else { - Common.showLog( "存储权限被拒绝" ) - } - } - - - } - fun checkStoragePermissionAndDoSomething() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && - ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED - ) { - requestStoragePermission.launch(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - // Android 10+ 不需要写权限,或者权限已获取 - // 执行写入文件操作 - Common.showLog("已获取存储权限") - } - } - - private fun checkSyswindow(context: Context) { - if (!Settings.canDrawOverlays(context)) { - val intent = Intent( - Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:${context.packageName}") - ) - startActivity(intent) - } - - } - - - private fun requestNotification() { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ContextCompat.checkSelfPermission( - this, - android.Manifest.permission.POST_NOTIFICATIONS - ) - != PackageManager.PERMISSION_GRANTED - ) { - requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) - } else { - // 权限已授予,可以发送通知 - Common.showLog("权限已授予,可以发送通知") - startForegroundService() - } - } else { - // Android 12 及以下,不需要请求权限 - Common.showLog("不需要请求权限") - startForegroundService() - } - } - - - private fun checkCamera() { - if (ContextCompat.checkSelfPermission( - this, - android.Manifest.permission.CAMERA - ) - != PackageManager.PERMISSION_GRANTED - ) { - cameraPermissionLauncher.launch(android.Manifest.permission.CAMERA) - } else { - Common.showLog("权限已授予 CAMERA") - - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - val mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data!!) - if (requestCode == SCREEN_CAPTURE_REQUEST_CODE && resultCode == RESULT_OK) { - - startRecording(mediaProjection) - }else if (requestCode == REQUEST_SCREENSHOT && resultCode == RESULT_OK && data != null) { -// ScreenCaptureHelper.startScreenCapture(this,mediaProjection) - } - } - - private fun startForegroundService() { -// FloatingWindowBridge.startAndBindService(this) - } - - fun startRecording(mediaProjection: MediaProjection) { - val screen = VideoFileHelper.getScreenInfo(this@MainActivity) - val width = VideoFileHelper.alignTo16(screen.width) - val height = VideoFileHelper.alignTo16(screen.height) - initRecorder(width, height) - mediaRecorder.start() - - val virtualDisplay = mediaProjection.createVirtualDisplay( - "ScreenRecord", - width, height, resources.displayMetrics.densityDpi, - DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - mediaRecorder.surface, null, null - ) - } - - fun stopRecording() { - Common.showLog("-------录屏完成.....") - mediaRecorder.stop() - mediaRecorder.reset() - } - - - private lateinit var mediaRecorder: MediaRecorder - - fun initRecorder(width: Int, height: Int) { - - val (videoUri, pfd) = VideoFileHelper.createVideoFile(this, Common.folderName) - tmpVideoUri = videoUri - - mediaRecorder = MediaRecorder().apply { - setAudioSource(MediaRecorder.AudioSource.MIC) - setVideoSource(MediaRecorder.VideoSource.SURFACE) - setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) - setOutputFile(pfd?.fileDescriptor) - setVideoSize(width, height) - setVideoEncoder(MediaRecorder.VideoEncoder.H264) - setAudioEncoder(MediaRecorder.AudioEncoder.AAC) - setVideoEncodingBitRate(8 * 1000 * 1000) - setVideoFrameRate(30) - prepare() - } - } - - override fun onDestroy() { - super.onDestroy() - - } - - - - - -} \ No newline at end of file diff --git a/app/src/main/java/com/audio/record/screen/test/activity/MainActivity1.kt b/app/src/main/java/com/audio/record/screen/test/activity/MainActivity1.kt index e5fd3dd..e7580ea 100644 --- a/app/src/main/java/com/audio/record/screen/test/activity/MainActivity1.kt +++ b/app/src/main/java/com/audio/record/screen/test/activity/MainActivity1.kt @@ -1,6 +1,5 @@ package com.audio.record.screen.test.activity -import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -13,24 +12,18 @@ import android.widget.ImageView import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat -import androidx.core.view.marginBottom -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.audio.record.screen.test.R import com.audio.record.screen.test.adapter.ViewPager2Adapter import com.audio.record.screen.test.base.BaseActivity import com.audio.record.screen.test.databinding.ActivityMain1Binding import com.audio.record.screen.test.dialog.DialogPermission -import com.audio.record.screen.test.fragment.MainFragment import com.audio.record.screen.test.service.ConnectionListener import com.audio.record.screen.test.service.FloatingCallback import com.audio.record.screen.test.service.FloatingWindowBridge import com.audio.record.screen.test.tool.Common -import com.audio.record.screen.test.tool.Extend.setMarginBottom import com.audio.record.screen.test.tool.Permission -import com.audio.record.screen.test.tool.ScreenCaptureHelper import com.audio.record.screen.test.viewmodel.MainViewModel -import com.audio.record.screen.test.viewmodel.PreviewViewModel import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener import com.google.android.material.tabs.TabLayoutMediator @@ -64,6 +57,8 @@ class MainActivity1 : BaseActivity(), ConnectionListener,F initLauncher() firstCheck() checkStoragePermissionAndDoSomething() + + } private fun initLauncher() { @@ -73,6 +68,7 @@ class MainActivity1 : BaseActivity(), ConnectionListener,F if (isGranted) { // 权限已授予 Common.showLog("已获取存储权限") + showPermissionDialog() // 执行写入文件操作 } else { @@ -83,14 +79,21 @@ class MainActivity1 : BaseActivity(), ConnectionListener,F requestNotificationLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { - startForegroundService() Common.showLog("权限授予") } else { Common.showLog("权限拒绝") } } - + screenCaptureLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == RESULT_OK && result.data != null) { + val data: Intent? = result.data + FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!) + startForegroundService() + } + } // screenCaptureLauncher = registerForActivityResult( // ActivityResultContracts.StartActivityForResult() // ) { result -> @@ -161,9 +164,10 @@ class MainActivity1 : BaseActivity(), ConnectionListener,F private fun showPermissionDialog() { if (isNotification && isOverlay) { + requestScreenPermission() return } - mPermissionDialog = mPermissionDialog ?: DialogPermission { + mPermissionDialog = mPermissionDialog ?: DialogPermission ({ when (it) { DialogPermission.type_ball -> { intentSysWindow(this@MainActivity1) @@ -173,11 +177,19 @@ class MainActivity1 : BaseActivity(), ConnectionListener,F requestNotificationLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) } } + }){ + //消失 + requestScreenPermission() } + mPermissionDialog?.show(supportFragmentManager, "") } - + private fun requestScreenPermission() { + val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager + val intent = mediaProjectionManager.createScreenCaptureIntent() + screenCaptureLauncher.launch(intent) + } //悬浮窗 private fun intentSysWindow(context: Context) { val intent = Intent( @@ -191,9 +203,7 @@ class MainActivity1 : BaseActivity(), ConnectionListener,F private fun firstCheck() { Permission.checkNotification(this@MainActivity1) { isNotification = it - if (it) { - startForegroundService() - } + } Permission.checkOvalApp(this@MainActivity1) { isOverlay = it diff --git a/app/src/main/java/com/audio/record/screen/test/activity/PreviewActivity.kt b/app/src/main/java/com/audio/record/screen/test/activity/PreviewActivity.kt deleted file mode 100644 index 61c6ebf..0000000 --- a/app/src/main/java/com/audio/record/screen/test/activity/PreviewActivity.kt +++ /dev/null @@ -1,183 +0,0 @@ -package com.audio.record.screen.test.activity - - -import android.graphics.RectF -import android.net.Uri -import android.view.View -import androidx.core.view.isVisible -import androidx.lifecycle.ViewModelProvider -import androidx.media3.common.MediaItem -import androidx.media3.common.PlaybackParameters -import androidx.media3.exoplayer.ExoPlayer -import androidx.navigation.fragment.NavHostFragment -import com.audio.record.screen.test.App -import com.audio.record.screen.test.R -import com.audio.record.screen.test.base.BaseActivity -import com.audio.record.screen.test.databinding.ActivityPreviewBinding -import com.audio.record.screen.test.tool.Common -import com.audio.record.screen.test.tool.FFmpegKitTool -import com.audio.record.screen.test.viewmodel.PreviewViewModel -import java.io.File -import kotlin.properties.Delegates - - -class PreviewActivity : BaseActivity(), View.OnClickListener { - - private lateinit var exoPlayer: ExoPlayer - - //将当前需要处理的原视频复制到内部存储,方便操作 - private lateinit var copyFile: File - - private var copyResult by Delegates.notNull() - private lateinit var viewModel: PreviewViewModel - override fun initBinding(): ActivityPreviewBinding = - ActivityPreviewBinding.inflate(layoutInflater) - - - override fun getFullColor(): Boolean = true - override fun onInitPadding(): Boolean = true - override fun onCreateInit() { - initPlay() - viewModel = ViewModelProvider(this)[PreviewViewModel::class.java] - binding.imPlay.setOnClickListener { - if (!binding.imPlay.isSelected) { - exoPlayer.play() - } else { - exoPlayer.pause() - } - binding.imPlay.isSelected = !binding.imPlay.isSelected - } - - val thumbDir = File(App.instanceApp.cacheDir, "thumb").apply { - if (!exists()) { - mkdir() - } else { - Common.deleteAllFilesInDirectory(this.absolutePath) - } - } -// val thumbAdapter = ThumbAdapter(this@PreviewActivity) -// binding.thumbRecycler.apply { -// layoutManager = -// LinearLayoutManager(this@PreviewActivity, RecyclerView.HORIZONTAL, false) -// adapter = thumbAdapter -// } - val open = App.instanceApp.assets.open("record_1748398994963.mp4") - copyFile = File(App.instanceApp.cacheDir, "temp_video.mp4") - //原视频复制到内部存储 - FFmpegKitTool.copy(copyFile, open, thumbDir.absolutePath) { - Common.showLog("--------copy success") - copyResult = it - } - - - initListener() - viewModel.cropRatioText.observe(this) { - if (it.equals("原始")) { - val videoRatio = Common.getVideoRatio(copyFile.absolutePath) - binding.cropView.setAspectRatio(videoRatio) - } else { - val split = it.split(":") - binding.cropView.setAspectRatio(split[0].toFloat() / split[1].toFloat()) - } - } - viewModel.saveCrop.observe(this){ - - val cropFile = File(App.instanceApp.cacheDir, "crop_video_${System.currentTimeMillis()}.mp4") - val videoWH = Common.getVideoWH(copyFile.absolutePath) - - val rawCropRect = binding.cropView.getCropRectInVideoCoords(videoWH.first , - videoWH.second - ) - - val x = rawCropRect?.left?.toInt() ?: 0 - val y = rawCropRect?.top?.toInt() ?: 0 - val w = rawCropRect?.width()?.toInt() ?: 0 - val h = rawCropRect?.height()?.toInt() ?: 0 - - Common.showLog("-------videoWH w=${videoWH.first} h=${videoWH.second} x=${x} y = $y w= $w h= $h") - FFmpegKitTool.cropVideo(copyFile.absolutePath,cropFile.absolutePath,x,y,w,h) - - - } - viewModel.changeSpeed.observe(this){ - exoPlayer.playbackParameters = PlaybackParameters(it, it) - } - - } - - private fun initListener() { - val navHostFragment = supportFragmentManager - .findFragmentById(R.id.nav_host_fragment) as NavHostFragment - val navController = navHostFragment.navController - navController.addOnDestinationChangedListener { controller, destination, arguments -> - Common.showLog("NavControllerListener 当前的目的地: id=${destination.id} name=${destination.displayName} label=${destination.label}") - binding.cropView.isVisible = false - when (destination.id) { - R.id.fragmentCut -> { - viewModel.updateCopyResult(Pair(copyResult, copyFile.absolutePath)) - Common.showLog("----fragmentCut") - } - - R.id.fragmentCropping -> { - Common.showLog("----fragmentCropping") - initCropView() - - } - - R.id.fragmentVolume -> { - Common.showLog("----fragmentVolume") - viewModel.updateCopyResult(Pair(copyResult, copyFile.absolutePath)) - } - - R.id.fragmentSpeed -> { - Common.showLog("----fragmentSpeed") - viewModel.updateCopyResult(Pair(copyResult, copyFile.absolutePath)) - } - } - } - } - - - private fun initCropView() { - binding.cropView.isVisible = true - // 延迟执行以确保布局完成 - binding.playerView.post { - val contentFrame: View = - binding.playerView.findViewById(androidx.media3.ui.R.id.exo_content_frame) - if (contentFrame != null) { - // 获取在 cropView 坐标系中的显示区域 - val videoRect = RectF() - val contentLoc = IntArray(2) - val cropLoc = IntArray(2) - contentFrame.getLocationOnScreen(contentLoc) - binding.cropView.getLocationOnScreen(cropLoc) - val offsetX = (contentLoc[0] - cropLoc[0]).toFloat() - val offsetY = (contentLoc[1] - cropLoc[1]).toFloat() - videoRect.left = offsetX - videoRect.top = offsetY - videoRect.right = offsetX + contentFrame.width - videoRect.bottom = offsetY + contentFrame.height - // 设置裁剪区域限制边界 - binding.cropView.setVideoDisplayBounds(videoRect) - } - } - } - - - private fun initPlay() { - val uri = Uri.parse("asset:///record_1748398994963.mp4") - exoPlayer = ExoPlayer.Builder(this).build() - - binding.playerView.player = exoPlayer - val mediaItem = MediaItem.fromUri(uri) - exoPlayer.setMediaItem(mediaItem) - exoPlayer.prepare() -// exoPlayer.playWhenReady = true - } - - override fun onClick(p0: View?) { - - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/audio/record/screen/test/activity/ScreenPermissionActivity.kt b/app/src/main/java/com/audio/record/screen/test/activity/ScreenPermissionActivity.kt index 053493c..ba65be7 100644 --- a/app/src/main/java/com/audio/record/screen/test/activity/ScreenPermissionActivity.kt +++ b/app/src/main/java/com/audio/record/screen/test/activity/ScreenPermissionActivity.kt @@ -2,12 +2,18 @@ package com.audio.record.screen.test.activity import android.content.Context import android.content.Intent +import android.graphics.Color import android.media.projection.MediaProjectionManager +import android.os.Build import android.os.Bundle import android.os.PersistableBundle +import android.view.View +import android.view.WindowInsets +import android.view.WindowInsetsController import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.WindowCompat import com.audio.record.screen.test.base.BaseActivity import com.audio.record.screen.test.databinding.ActivityPlayBinding import com.audio.record.screen.test.service.FloatingWindowBridge @@ -29,14 +35,46 @@ class ScreenPermissionActivity : AppCompatActivity() { } private var withAudio = false + private lateinit var mediaProjectionManager:MediaProjectionManager + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { +// Common.hideSystemBars(this) +// Common.setStatusBarTextColor(this, false) + setFullScreenTransparent() + } + } + private fun setFullScreenTransparent() { + WindowCompat.setDecorFitsSystemWindows(window, false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.insetsController?.let { + it.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) + it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + } else { + @Suppress("DEPRECATION") + window.decorView.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + } + + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Common.setStatusBarTextColor(this, true) +// Common.setStatusBarTextColor(this, true) + mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager withAudio = intent.getBooleanExtra(key_with_audio, false) screenCaptureLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> + Common.showLog("--------------screenCaptureLauncher") if (result.resultCode == RESULT_OK && result.data != null) { val data: Intent? = result.data FloatingWindowBridge.updateMediaProjection(result.resultCode, data!!) @@ -63,7 +101,7 @@ class ScreenPermissionActivity : AppCompatActivity() { private fun requestScreenPermission() { - val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager + Common.showLog("--------------requestScreenPermission") val intent = mediaProjectionManager.createScreenCaptureIntent() screenCaptureLauncher.launch(intent) } diff --git a/app/src/main/java/com/audio/record/screen/test/dialog/DialogPermission.kt b/app/src/main/java/com/audio/record/screen/test/dialog/DialogPermission.kt index d8f230c..4e4784c 100644 --- a/app/src/main/java/com/audio/record/screen/test/dialog/DialogPermission.kt +++ b/app/src/main/java/com/audio/record/screen/test/dialog/DialogPermission.kt @@ -1,5 +1,6 @@ package com.audio.record.screen.test.dialog +import android.content.DialogInterface import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Bundle @@ -11,7 +12,7 @@ import com.audio.record.screen.test.databinding.DialogPermissionBinding import com.audio.record.screen.test.tool.Permission import com.google.android.material.bottomsheet.BottomSheetDialogFragment -class DialogPermission(private var mClickType: (type: Int) -> Unit) : BottomSheetDialogFragment() { +class DialogPermission(private var mClickType: (type: Int) -> Unit,private var onDismiss:()->Unit) : BottomSheetDialogFragment() { private lateinit var vb: DialogPermissionBinding override fun onCreateView( inflater: LayoutInflater, @@ -28,6 +29,7 @@ class DialogPermission(private var mClickType: (type: Int) -> Unit) : BottomShee val dialog = dialog if (dialog != null) { dialog.setCanceledOnTouchOutside(false) + dialog.setCancelable(false) val window = dialog.window if (window != null) { window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) @@ -39,6 +41,11 @@ class DialogPermission(private var mClickType: (type: Int) -> Unit) : BottomShee } } + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + onDismiss.invoke() + } + private fun init() { vb.run { diff --git a/app/src/main/java/com/audio/record/screen/test/fragment/CropFragment.kt b/app/src/main/java/com/audio/record/screen/test/fragment/CropFragment.kt deleted file mode 100644 index 43bb318..0000000 --- a/app/src/main/java/com/audio/record/screen/test/fragment/CropFragment.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.audio.record.screen.test.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.NavController -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.audio.record.screen.test.R -import com.audio.record.screen.test.adapter.CropAdapter -import com.audio.record.screen.test.base.BaseFragment -import com.audio.record.screen.test.databinding.FragmentCropBinding -import com.audio.record.screen.test.viewmodel.PreviewViewModel - -class CropFragment : BaseFragment(), View.OnClickListener { - - - private lateinit var navController: NavController - private lateinit var viewModel: PreviewViewModel - - companion object { - - @JvmStatic - fun newInstance() = - CropFragment().apply { - arguments = Bundle().apply { - - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { - - } - } - - - override fun initBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCropBinding = - FragmentCropBinding.inflate(inflater, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - navController = findNavController() - - viewModel = ViewModelProvider(requireActivity())[PreviewViewModel::class.java] - binding.recycler.run { - val stringList = resources.getStringArray(R.array.crop_text).toList() - adapter = CropAdapter(requireContext()) { - viewModel.updateCropText(it) - - }.apply { updateData(stringList) } - layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) - } - initClick() - } - - private fun initClick() { - binding.imClose.setOnClickListener(this) - binding.imSave.setOnClickListener(this) - } - - override fun onClick(v: View?) { - v?.let { - if (it == binding.imSave) { - viewModel.updateClickCropSave(true) - } else if (it == binding.imClose) { - navController.navigateUp() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/audio/record/screen/test/fragment/CutFragment.kt b/app/src/main/java/com/audio/record/screen/test/fragment/CutFragment.kt deleted file mode 100644 index 6287b85..0000000 --- a/app/src/main/java/com/audio/record/screen/test/fragment/CutFragment.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.audio.record.screen.test.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.NavController -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.audio.record.screen.test.App -import com.audio.record.screen.test.adapter.ThumbAdapter -import com.audio.record.screen.test.base.BaseFragment -import com.audio.record.screen.test.databinding.FragmentCutBinding -import com.audio.record.screen.test.tool.Common -import com.audio.record.screen.test.tool.FFmpegKitTool -import com.audio.record.screen.test.viewmodel.PreviewViewModel -import com.jaygoo.widget.OnRangeChangedListener -import com.jaygoo.widget.RangeSeekBar -import java.io.File -import kotlin.properties.Delegates - -class CutFragment : BaseFragment() ,View.OnClickListener{ - //毫秒单位 - private var leftV by Delegates.notNull() - private lateinit var viewModel: PreviewViewModel - private var rightV by Delegates.notNull() - private var param1: String? = null - private var param2: String? = null - private lateinit var copyFilePath: String - private lateinit var navController:NavController - companion object { - - @JvmStatic - fun newInstance() = - CutFragment().apply { - arguments = Bundle().apply { -// putString(ARG_PARAM1, param1) -// putString(ARG_PARAM2, param2) - } - } - } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { -// param1 = it.getString(ARG_PARAM1) -// param2 = it.getString(ARG_PARAM2) - } - } - - - override fun initBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCutBinding = - FragmentCutBinding.inflate(inflater, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - navController = findNavController() - viewModel = ViewModelProvider(requireActivity())[PreviewViewModel::class.java] - initThumb() - initClick() - } - - - private fun initThumb(){ - val thumbDir = File(App.instanceApp.cacheDir, "thumb") - val thumbAdapter = ThumbAdapter(requireContext()) - binding.thumbRecycler.apply { - layoutManager = - LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) - adapter = thumbAdapter - } -// val open = App.instanceApp.assets.open("temp_video.mp4") -// tempFile = File(App.instanceApp.cacheDir, "temp_video.mp4") - viewModel.copySuccess.observe(requireActivity()){ - if (it.first) { - Common.showLog("-------11111111111111") - copyFilePath = it.second - val allImagePaths = Common.getNaturallySortedThumbFiles(thumbDir.absolutePath) - thumbAdapter.updateData(allImagePaths) - initSeekBar() - } - } - - } - private fun initClick() { - binding.imClose.setOnClickListener(this) - binding.imSave.setOnClickListener(this) - } - private fun initSeekBar() { - val durationMs = Common.getVideoDurationMs(copyFilePath) - -// val millisToSeconds = Common.millisToSeconds(durationMs) - Common.showLog("----durationMs=${durationMs} ") - binding.rangeSlider.run { - durationMs.toFloat().let { - setRange(0f, it) - leftSeekBar?.setIndicatorText(Common.formatSeconds(0f)) - rightSeekBar?.setIndicatorText(Common.formatSeconds(it)) - setProgress(0f, it) - - } - - - setOnRangeChangedListener(object : OnRangeChangedListener { - override fun onRangeChanged( - view: RangeSeekBar?, - leftValue: Float, - rightValue: Float, - isFromUser: Boolean - ) { - view?.leftSeekBar?.setIndicatorText(Common.formatSeconds(leftValue)) - view?.rightSeekBar?.setIndicatorText(Common.formatSeconds(rightValue)) - leftV = leftValue - rightV = rightValue - - } - - override fun onStartTrackingTouch(view: RangeSeekBar?, isLeft: Boolean) { - - } - - override fun onStopTrackingTouch(view: RangeSeekBar?, isLeft: Boolean) { - - } - - }) - } - } - - override fun onClick(p0: View?) { - p0?.let { - if (it == binding.imSave) { - trimVideoFile() - }else if(it == binding.imClose){ - navController.navigateUp() - } - } - } - fun trimVideoFile() { - val resultFile = File(requireContext().cacheDir, "test_${System.currentTimeMillis()}.mp4") - val left = leftV.toString() - val right = rightV.toString() - Common.showLog("------left=${left} right=${right}") -// FFmpegKitTool.trimVideo(tempFile.absolutePath, resultFile.absolutePath, left, right) - FFmpegKitTool.cropVideoWithFFmpeg(copyFilePath, resultFile.absolutePath,leftV,rightV) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/audio/record/screen/test/fragment/SpeedFragment.kt b/app/src/main/java/com/audio/record/screen/test/fragment/SpeedFragment.kt deleted file mode 100644 index 1584a9c..0000000 --- a/app/src/main/java/com/audio/record/screen/test/fragment/SpeedFragment.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.audio.record.screen.test.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.SeekBar -import android.widget.SeekBar.OnSeekBarChangeListener -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.NavController -import androidx.navigation.fragment.findNavController -import com.audio.record.screen.test.App -import com.audio.record.screen.test.base.BaseFragment -import com.audio.record.screen.test.databinding.FragmentSpeedBinding -import com.audio.record.screen.test.tool.Common -import com.audio.record.screen.test.tool.FFmpegKitTool -import com.audio.record.screen.test.viewmodel.PreviewViewModel -import java.io.File - -class SpeedFragment : BaseFragment(), View.OnClickListener { - - - private lateinit var viewModel: PreviewViewModel - private lateinit var navController: NavController - private lateinit var copyFilePath: String - private var speed = 1f - - companion object { - - @JvmStatic - fun newInstance() = - SpeedFragment().apply { - arguments = Bundle().apply { -// putString(ARG_PARAM1, param1) -// putString(ARG_PARAM2, param2) - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { -// param1 = it.getString(ARG_PARAM1) -// param2 = it.getString(ARG_PARAM2) - } - } - - - override fun initBinding( - inflater: LayoutInflater, - container: ViewGroup? - ): FragmentSpeedBinding = - FragmentSpeedBinding.inflate(inflater, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewModel = ViewModelProvider(requireActivity())[PreviewViewModel::class.java] - navController = findNavController() - - initClick() - viewModel.copySuccess.observe(requireActivity()){ - if (it.first) { - Common.showLog("-------11111111111111") - copyFilePath = it.second - - } - } - } - - - private fun initClick() { - binding.imClose.setOnClickListener(this) - binding.imSave.setOnClickListener(this) - binding.seekbar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - speed = 0.5f + (progress / 150f) * 1.5f - binding.progressText.text = "${speed}X" - viewModel.updateSpeed(speed) - Common.showLog("--------progress=${progress} speed=${speed}") - } - - override fun onStartTrackingTouch(seekBar: SeekBar?) { - - } - - override fun onStopTrackingTouch(seekBar: SeekBar?) { - - } - - }) - } - - - override fun onClick(p0: View?) { - p0?.let { - if (it == binding.imSave) { - val volumeFile = File(App.instanceApp.cacheDir, "speed_video_${System.currentTimeMillis()}.mp4") - FFmpegKitTool.buildSpeedCommand(copyFilePath,volumeFile.absolutePath,speed) - } else if (it == binding.imClose) { - navController.navigateUp() - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/audio/record/screen/test/fragment/VolumeFragment.kt b/app/src/main/java/com/audio/record/screen/test/fragment/VolumeFragment.kt deleted file mode 100644 index 818a040..0000000 --- a/app/src/main/java/com/audio/record/screen/test/fragment/VolumeFragment.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.audio.record.screen.test.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.SeekBar -import android.widget.SeekBar.OnSeekBarChangeListener -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.NavController -import androidx.navigation.fragment.findNavController -import com.audio.record.screen.test.App -import com.audio.record.screen.test.base.BaseFragment -import com.audio.record.screen.test.databinding.FragmentVolumeBinding -import com.audio.record.screen.test.tool.Common -import com.audio.record.screen.test.tool.FFmpegKitTool -import com.audio.record.screen.test.viewmodel.PreviewViewModel -import java.io.File - -class VolumeFragment : BaseFragment(), View.OnClickListener { - - - private lateinit var viewModel: PreviewViewModel - private lateinit var navController: NavController - private lateinit var copyFilePath: String - companion object { - - @JvmStatic - fun newInstance() = - VolumeFragment().apply { - arguments = Bundle().apply { -// putString(ARG_PARAM1, param1) -// putString(ARG_PARAM2, param2) - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { -// param1 = it.getString(ARG_PARAM1) -// param2 = it.getString(ARG_PARAM2) - } - } - - - override fun initBinding( - inflater: LayoutInflater, - container: ViewGroup? - ): FragmentVolumeBinding = - FragmentVolumeBinding.inflate(inflater, container, false) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewModel = ViewModelProvider(requireActivity())[PreviewViewModel::class.java] - navController = findNavController() - - initClick() - viewModel.copySuccess.observe(requireActivity()){ - if (it.first) { - Common.showLog("-------11111111111111") - copyFilePath = it.second - - } - } - } - - - private fun initClick() { - binding.imClose.setOnClickListener(this) - binding.imSave.setOnClickListener(this) - binding.seekbar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - binding.progressText.text = "${progress}%" - } - - override fun onStartTrackingTouch(seekBar: SeekBar?) { - - } - - override fun onStopTrackingTouch(seekBar: SeekBar?) { - - } - - }) - } - - - override fun onClick(p0: View?) { - p0?.let { - if (it == binding.imSave) { - val volumeFile = File(App.instanceApp.cacheDir, "volume_video_${System.currentTimeMillis()}.mp4") - val volume = (binding.seekbar.progress.coerceIn(0, 200)) / 100.0f - FFmpegKitTool.setVideVolume(copyFilePath,volumeFile.absolutePath,volume) - - } else if (it == binding.imClose) { - navController.navigateUp() - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordNormalFragment.kt b/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordNormalFragment.kt index 3e18810..740c188 100644 --- a/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordNormalFragment.kt +++ b/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordNormalFragment.kt @@ -197,10 +197,12 @@ class RecordNormalFragment : BaseFragment(), Floati } private fun setScreenshot(boolean: Boolean) { + Common.showLog("----------setScreenshot=${boolean}") if (boolean) { - requestRecordPermission(ConstValue.type_show_screenshot){ - FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot) - } +// requestRecordPermission(ConstValue.type_show_screenshot){ +//// FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot) +// } + FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_show_screenshot) } else { FloatingWindowBridge.sendCommand(FloatingWindowBridge.COMMEND_hide_screenshot) @@ -217,7 +219,7 @@ class RecordNormalFragment : BaseFragment(), Floati private fun startRecorder() { requestRecordPermission( ConstValue.type_record){ - startCountDown() +// startCountDown() } } @@ -227,13 +229,9 @@ class RecordNormalFragment : BaseFragment(), Floati */ private fun requestRecordPermission(type:Int,check:(()->Unit)? = null){ // TODO: requestRecordPermission - if (FloatingWindowBridge.getMediaProjection() == null) { - val captureIntent = mediaProjectionManager.createScreenCaptureIntent() - pendingType = type - recorderLauncher.launch(captureIntent) - }else{ - check?.invoke() - } + val captureIntent = mediaProjectionManager.createScreenCaptureIntent() + pendingType = type + recorderLauncher.launch(captureIntent) } diff --git a/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordingFragment.kt b/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordingFragment.kt index 80f1ff7..1da0bb8 100644 --- a/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordingFragment.kt +++ b/app/src/main/java/com/audio/record/screen/test/fragment/child/RecordingFragment.kt @@ -108,6 +108,7 @@ class RecordingFragment : BaseFragment(), FloatingCall override fun onUpdateRecordTime(time: String) { super.onUpdateRecordTime(time) + Common.showLog("=======onUpdateRecordTime 时间更新") binding.tvTimer.text = time } diff --git a/app/src/main/java/com/audio/record/screen/test/service/ScreenRecordService.kt b/app/src/main/java/com/audio/record/screen/test/service/ScreenRecordService.kt index 16d2af2..17cd972 100644 --- a/app/src/main/java/com/audio/record/screen/test/service/ScreenRecordService.kt +++ b/app/src/main/java/com/audio/record/screen/test/service/ScreenRecordService.kt @@ -146,7 +146,7 @@ class ScreenRecordService : Service() { private val timeUpdateRunnable = object : Runnable { override fun run() { - + Common.showLog("=======timeUpdateRunnable 时间更新 isPause=${isPause}") val currentRecordingTimeInMillis = System.currentTimeMillis() - recordingStartTime - totalPausedTime val elapsed = currentRecordingTimeInMillis / 1000 @@ -156,8 +156,10 @@ class ScreenRecordService : Service() { callbacks.forEach { it.get()?.onUpdateRecordTime(onRecordingTimeChanged) } - if (!isPause) + if (!isPause){ mRecorderHandler.postDelayed(this, updateInterval) + } + } } @@ -277,8 +279,9 @@ class ScreenRecordService : Service() { */ private fun startScreenshot(showView: Boolean) { // TODO: 开始截屏 - mIntent?.let { intent -> - mediaProjectionManager.getMediaProjection(mCode, intent)?.let { mediaProjection -> + + createMediaProject { + it?.let { mediaProjection-> hideScreenshot() val intent = Intent(this, ScreenshotAnimActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -291,8 +294,10 @@ class ScreenRecordService : Service() { if (showView) showScreenshot() } + } } + } @SuppressLint("ClickableViewAccessibility") @@ -384,7 +389,7 @@ class ScreenRecordService : Service() { } Common.showLog("-------layoutParamsBall.x=${layoutParamsBall.x} layoutParamsBall.y=${layoutParamsBall.y}") } - if (!isBallViewAdded&&!isBallExpandViewAdded) { + if (!isBallViewAdded && !isBallExpandViewAdded) { windowManager.addView(ballView, layoutParamsBall) isBallViewAdded = true } @@ -482,6 +487,7 @@ class ScreenRecordService : Service() { } layoutParamsBallExpand.y = aY + updateBall() windowManager.addView(ballViewExpand, layoutParamsBallExpand) isBallExpandViewAdded = true delayRemove { @@ -529,7 +535,8 @@ class ScreenRecordService : Service() { windowManager, true ) { - startScreenshot(true) + ballType = ConstValue.type_screenshot + intentPermission(false) } } if (!isScreenshotViewAdded) { @@ -662,23 +669,27 @@ class ScreenRecordService : Service() { //倒计时结束后,开启录制 Common.showLog("Service--- 倒计时结束") hideCountDown() - mIntent?.let { intent -> - mediaProjectionManager.getMediaProjection(mCode, intent) - ?.let { media -> - mediaProjection = media - startRecording() - callbacks.forEach { - it.get()?.onStartRecording() - } - } + startRecording() + callbacks.forEach { + it.get()?.onStartRecording() } - } } } + + private fun createMediaProject(onAction: (med: MediaProjection?) -> Unit) { + mIntent?.let { intent -> + mediaProjectionManager.getMediaProjection(mCode, intent) + ?.let { media -> + mediaProjection = media + onAction.invoke(media) + } ?: onAction.invoke(null) + } ?: onAction.invoke(null) + } + private fun animateNext( countdownValues: Array, tvCountdown: TextView, @@ -741,37 +752,40 @@ class ScreenRecordService : Service() { totalPausedTime = 0L mRecorderHandler.post(timeUpdateRunnable) - mediaProjection?.let { - it.registerCallback(object : MediaProjection.Callback() { - override fun onStop() { - Common.showLog("MediaProjection 被系统或用户停止") - // 这里应该释放 MediaRecorder 和 VirtualDisplay 等 - stopRecording() + createMediaProject{ curmed-> + mediaProjection?.let { + it.registerCallback(object : MediaProjection.Callback() { + override fun onStop() { + Common.showLog("MediaProjection 被系统或用户停止") + // 这里应该释放 MediaRecorder 和 VirtualDisplay 等 + stopRecording() - } - }, Handler(Looper.getMainLooper())) - virtualDisplay = it.createVirtualDisplay( - "ScreenRecord", - width, height, resources.displayMetrics.densityDpi, - DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - mediaRecorder.surface, object : VirtualDisplay.Callback() { - override fun onPaused() { - super.onPaused() - Common.showLog("--VirtualDisplay.Callback..onPaused...") } + }, Handler(Looper.getMainLooper())) + virtualDisplay = it.createVirtualDisplay( + "ScreenRecord", + width, height, resources.displayMetrics.densityDpi, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, + mediaRecorder.surface, object : VirtualDisplay.Callback() { + override fun onPaused() { + super.onPaused() + Common.showLog("--VirtualDisplay.Callback..onPaused...") + } - override fun onResumed() { - super.onResumed() - Common.showLog("--VirtualDisplay.Callback..onResumed...") - } + override fun onResumed() { + super.onResumed() + Common.showLog("--VirtualDisplay.Callback..onResumed...") + } - override fun onStopped() { - super.onStopped() - Common.showLog("--VirtualDisplay.Callback..onStopped...") - } - }, null - ) + override fun onStopped() { + super.onStopped() + Common.showLog("--VirtualDisplay.Callback..onStopped...") + } + }, null + ) + } } + } @@ -922,7 +936,11 @@ class ScreenRecordService : Service() { fun getViewStatus() { callbacks.forEach { - it.get()?.onRefreshViewShow(isWebcamViewAdded, isScreenshotViewAdded, isBallViewAdded||isBallExpandViewAdded) + it.get()?.onRefreshViewShow( + isWebcamViewAdded, + isScreenshotViewAdded, + isBallViewAdded || isBallExpandViewAdded + ) } } diff --git a/app/src/main/java/com/audio/record/screen/test/tool/Common.kt b/app/src/main/java/com/audio/record/screen/test/tool/Common.kt index e39b3fd..c228636 100644 --- a/app/src/main/java/com/audio/record/screen/test/tool/Common.kt +++ b/app/src/main/java/com/audio/record/screen/test/tool/Common.kt @@ -1,5 +1,6 @@ package com.audio.record.screen.test.tool +import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent @@ -17,6 +18,8 @@ import android.view.KeyCharacterMap import android.view.KeyEvent import android.view.View import android.view.ViewConfiguration +import android.view.WindowInsets +import android.view.WindowInsetsController import android.view.WindowManager import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat @@ -40,26 +43,6 @@ object Common { } fun setStatusBarTextColor(activity: Activity, dark: Boolean) { -// val window = activity.window -// val decor = window.decorView -// -// // 设置状态栏图标颜色(深色图标表示浅色背景) -// var flags = decor.systemUiVisibility -// flags = if (dark) { -// flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR -// } else { -// flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() -// } -// // 保持布局全屏 -// flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN -// decor.systemUiVisibility = flags -// -// // 去除 TRANSLUCENT_STATUS,使用透明背景更现代 -// window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) -// window.statusBarColor = Color.TRANSPARENT - - - val window = activity.window val decor = window.decorView @@ -428,4 +411,30 @@ object Common { } + + @SuppressLint("InlinedApi") + fun hideSystemBars(activity: Activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Android 11+ + activity.window.setDecorFitsSystemWindows(false) + val controller = activity.window.insetsController + controller?.let { + it.hide(WindowInsets.Type.navigationBars() or WindowInsets.Type.statusBars()) + it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + } else { + // Android 7 - 10 + @Suppress("DEPRECATION") + activity.window.decorView.systemUiVisibility = ( + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_FULLSCREEN + ) + } + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/audio/record/screen/test/tool/FFmpegKitTool.kt b/app/src/main/java/com/audio/record/screen/test/tool/FFmpegKitTool.kt deleted file mode 100644 index d8bfb10..0000000 --- a/app/src/main/java/com/audio/record/screen/test/tool/FFmpegKitTool.kt +++ /dev/null @@ -1,224 +0,0 @@ -package com.audio.record.screen.test.tool - -import android.media.MediaMetadataRetriever -import android.os.Handler -import android.os.Looper -import android.util.Log -import com.arthenica.ffmpegkit.FFmpegKit -import com.arthenica.ffmpegkit.ReturnCode -import com.audio.record.screen.test.App -import java.io.File -import java.io.FileOutputStream -import java.io.InputStream -import java.util.concurrent.TimeUnit - -object FFmpegKitTool { - - val mainHandler = Handler(Looper.getMainLooper()) - - fun copy( - tempFile: File, - inputStream: InputStream, - thumbDir: String, - result: (ok: Boolean) -> Unit - ) { - - FileOutputStream(tempFile).use { outputStream -> - inputStream.copyTo(outputStream) - outputStream.flush() - extractThumbnailsFFmpeg(tempFile.absolutePath, thumbDir, result) - } - - } - - fun createThumb(inputPath: String, outputDir: String, result: (ok: Boolean) -> Unit) { -// val inputPath = "/storage/emulated/0/Movies/sample.mp4" -// val outputDir = "/storage/emulated/0/Movies/thumbs" // 请确保已创建 - val cmd = "-i $inputPath -vf fps=10 $outputDir/thumb_%04d.jpg" - - FFmpegKit.executeAsync(cmd) { session -> - val returnCode = session.returnCode - if (ReturnCode.isSuccess(returnCode)) { - Common.showLog("FFmpegKit 缩略图生成成功 ${Thread.currentThread().name}") - result.invoke(true) - } else { - result.invoke(false) - Common.showLog("FFmpegKit 缩略图生成失败:${session.failStackTrace}") - } - } - - } - - - /** - * 截取视频缩略图,均分6张图 - */ - fun extractThumbnailsFFmpeg( - videoPath: String, - outputDir: String, - result: (ok: Boolean) -> Unit - ) { - var count = 0 - val durationMs = Common.getVideoDurationMs(videoPath) - val durationSec = durationMs / 1000.0 - val step = durationSec / 7 // 均分6帧,跳过首尾 - Common.showLog("durationSec $durationSec step $step") - for (i in 1..6) { - val timestamp = i * step - val outputPath = "$outputDir/thumb_$i.jpg" - Common.showLog("i = $i timestamp $timestamp ") - val cmd = "-ss $timestamp -i \"$videoPath\" -frames:v 1 -q:v 2 \"$outputPath\"" - FFmpegKit.executeAsync(cmd) { session -> - val returnCode = session.returnCode - count++ - if (count == 6) { - mainHandler.post { - if (returnCode.isValueSuccess) { - result.invoke(true) - Common.showLog("FFmpeg Frame $i saved to $outputPath") - } else { - result.invoke(false) - Common.showLog("FFmpeg Failed to extract frame $i") - } - - } - } - - - } - } - } - - - /** - * 剪切视频时长 - * @param startTime - */ - fun cropVideoWithFFmpeg( - inputPath: String, - outputPath: String, - startMs: Float, - endMs: Float - ) { - val durationMs = endMs - startMs - if (durationMs <= 0) { - println("结束时间必须大于开始时间") - return - } - val formatFloatTime = formatFloatTime(durationMs) - val startTime = formatFloatTime(startMs) - val endTime = formatFloatTime(endMs) - val command = - "-ss $startTime -i \"$inputPath\" -to $formatFloatTime -c:v copy -c:a copy \"$outputPath\"" - Common.showLog("--command=${command}") - - FFmpegKit.executeAsync(command) { session -> - val returnCode = session.returnCode - if (returnCode.isValueSuccess) { - Common.showLog("裁剪成功: $outputPath") - } else { - Common.showLog("裁剪失败: ${session.failStackTrace}") - } - } - } - - // 毫秒转为 ffmpeg 时间格式:hh:mm:ss.S - fun formatFloatTime(ms: Float): String { - val totalSeconds = ms / 1000f - val hours = (totalSeconds / 3600).toInt() - val minutes = ((totalSeconds % 3600) / 60).toInt() - val seconds = (totalSeconds % 60).toInt() - val tenths = ((totalSeconds % 1) * 10).toInt() // 保留一位小数 - - return String.format("%02d:%02d:%02d.%01d", hours, minutes, seconds, tenths) - } -// -// fun trimVideo(inputPath: String, outputPath: String, startTime: String, endTime: String) { -// val command = "-ss $startTime -i $inputPath -to $endTime -c copy $outputPath" -// -// Common.showLog("--command=${command}") -// -// FFmpegKit.executeAsync(command) { session -> -// when { -// ReturnCode.isSuccess(session.returnCode) -> { -// Log.d("FFmpegKit", "剪切成功!输出路径: $outputPath") -// } -// -// session.returnCode.value == ReturnCode.CANCEL -> { -// Log.w("FFmpegKit", "用户取消操作") -// } -// -// else -> { -// Log.e("FFmpegKit", "剪切失败. 错误日志: ${session.output}") -// } -// } -// } -// } - - - /** - * 裁切视频显示内容尺寸 - */ - fun cropVideo( - inputPath: String, - outputPath: String, - cropX: Int, - cropY: Int, - cropWidth: Int, - cropHeight: Int - ) { -// val cmd = "-i $inputPath -vf crop=$cropWidth:$cropHeight:$cropX:$cropY -c:a copy $outputPath" - val cmd = - "-i $inputPath -vf crop=$cropWidth:$cropHeight:$cropX:$cropY -c:v mpeg4 -qscale:v 2 -c:a copy $outputPath" - - Common.showLog("FFmpeg Crop 比例裁剪 $cmd") - FFmpegKit.executeAsync(cmd) { - if (ReturnCode.isSuccess(it.returnCode)) { - Common.showLog("FFmpeg Crop 比例裁剪成功 $outputPath") - } else { - Common.showLog("FFmpeg Crop 比例裁剪失败: $outputPath ${it.failStackTrace}") - } - } - } - - - /** - * 调整视频音量 - */ - fun setVideVolume( - inputPath: String, - outputPath: String, volumeLevel: Float - ) { -// val volumeLevel = 1.5 // 音量倍数(例如 1.5 表示音量加大 50%) - val cmd = "-y -i $inputPath -filter:a volume=$volumeLevel -c:v copy $outputPath" - Common.showLog("FFmpeg 音量 $cmd") - FFmpegKit.executeAsync(cmd) { session -> - val returnCode = session.returnCode - if (ReturnCode.isSuccess(returnCode)) { - Common.showLog("FFmpeg 音量调节成功") - } else { - Common.showLog("FFmpeg 音量调节失败: ${session.failStackTrace}") - } - } - - } - - /** - * 调整视频播放速度 - */ - fun buildSpeedCommand(inputPath: String, outputPath: String, speed: Float) { - val videoFilter = "setpts=${1 / speed}*PTS" - val audioFilter = "atempo=$speed" - val cmd = "-y -i $inputPath -filter_complex \"[0:v]$videoFilter[v];[0:a]$audioFilter[a]\" -map \"[v]\" -map \"[a]\" -preset ultrafast $outputPath" - Common.showLog("FFmpeg 播放速度调整 $cmd") - FFmpegKit.executeAsync(cmd) { session -> - if (ReturnCode.isSuccess(session.returnCode)) { - Common.showLog("FFmpeg 播放速度调整成功") - } else { - Common.showLog("FFmpeg 播放速度调整失败: ${session.failStackTrace}") - } - } - - } - -} \ No newline at end of file diff --git a/app/src/main/res/drawable/logo1.png b/app/src/main/res/drawable/logo1.png new file mode 100644 index 0000000..a0a294d Binary files /dev/null and b/app/src/main/res/drawable/logo1.png differ diff --git a/app/src/main/res/drawable/logo2.png b/app/src/main/res/drawable/logo2.png new file mode 100644 index 0000000..bb4dbbc Binary files /dev/null and b/app/src/main/res/drawable/logo2.png differ diff --git a/app/src/main/res/drawable/test.png b/app/src/main/res/drawable/test.png deleted file mode 100644 index f1dfb61..0000000 Binary files a/app/src/main/res/drawable/test.png and /dev/null differ diff --git a/app/src/main/res/layout/custom_controller.xml b/app/src/main/res/layout/custom_controller.xml index 96c7903..d9a5b54 100644 --- a/app/src/main/res/layout/custom_controller.xml +++ b/app/src/main/res/layout/custom_controller.xml @@ -11,8 +11,8 @@ android:layout_height="wrap_content" android:background="@drawable/play_message_background" android:orientation="horizontal" - android:paddingTop="10dp" - android:paddingBottom="10dp"> + android:paddingTop="35dp" + android:paddingBottom="15dp"> + android:src="@drawable/logo1" /> + android:src="@drawable/logo2" /> diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 099924a..4942a7b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -9,10 +9,12 @@ \ No newline at end of file