1.效果图:

2.添加依赖 

dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"    implementation 'androidx.appcompat:appcompat:1.1.0'    implementation 'androidx.core:core-ktx:1.0.2'    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'    testImplementation 'junit:junit:4.12'    androidTestImplementation 'androidx.test.ext:junit:1.1.1'    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'    api 'com.blankj:utilcode:1.24.4'}repositories {    mavenCentral()}

3.注册权限:

            

4.主界面,

test.aac是录屏的时候配的音乐,可以随便找另外一个放到assets文件夹里面进行替换
package com.ufi.pdioms.ztkotlinimport android.content.Intentimport android.content.res.AssetFileDescriptorimport android.media.MediaPlayerimport android.os.Buildimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.util.Logimport android.widget.Toastimport com.blankj.utilcode.util.PathUtilsimport kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() {    // https://github.com/fanqilongmoli/AndroidScreenRecord    private var screenRecordHelper: ScreenRecordHelper? = null    private val afdd:AssetFileDescriptor by lazy { assets.openFd("test.aac") }    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        btnStart.setOnClickListener {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                if (screenRecordHelper == null) {                    screenRecordHelper = ScreenRecordHelper(this, object : ScreenRecordHelper.OnVideoRecordListener {                        override fun onBeforeRecord() {                        }                        override fun onStartRecord() {                            play()                        }                        override fun onCancelRecord() {                            releasePlayer()                        }                        override fun onEndRecord() {                            releasePlayer()                        }                    }, PathUtils.getExternalStoragePath() + "/fanqilong")                }                screenRecordHelper?.apply {                    if (!isRecording) {                        // 如果你想录制音频(一定会有环境音量),你可以打开下面这个限制,并且使用不带参数的 stopRecord()//                        recordAudio = true                        startRecord()                    }                }            } else {                Toast.makeText(this@MainActivity.applicationContext, "sorry,your phone does not support recording screen", Toast.LENGTH_LONG).show()            }        }        btnStop.setOnClickListener {            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                screenRecordHelper?.apply {                    if (isRecording) {                        if (mediaPlayer != null) {                            // 如果选择带参数的 stop 方法,则录制音频无效                            stopRecord(mediaPlayer!!.duration.toLong(), 15 * 1000, afdd)                        } else {                            stopRecord()                        }                    }                }            }        }    }    private fun play() {        mediaPlayer = MediaPlayer()        try {            mediaPlayer?.apply {                this.reset()                this.setDataSource(afdd.fileDescriptor, afdd.startOffset, afdd.length)                this.isLooping = true                this.prepare()                this.start()            }        } catch (e: Exception) {            Log.d("fanqilong", "播放音乐失败")        } finally {        }    }    // 音频播放    private var mediaPlayer: MediaPlayer? = null    private fun releasePlayer() {        mediaPlayer?.apply {            stop()            release()        }        mediaPlayer = null    }    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {        super.onActivityResult(requestCode, resultCode, data)        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && data != null) {            screenRecordHelper?.onActivityResult(requestCode, resultCode, data)        }    }    override fun onDestroy() {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            screenRecordHelper?.clearAll()        }        afdd.close()        super.onDestroy()    }}

5.录屏代码

package com.ufi.pdioms.ztkotlinimport android.app.Activityimport android.content.Contextimport android.content.Intentimport android.content.pm.PackageManagerimport android.content.res.AssetFileDescriptorimport android.hardware.display.DisplayManagerimport android.hardware.display.VirtualDisplayimport android.media.*import android.media.projection.MediaProjectionimport android.media.projection.MediaProjectionManagerimport android.net.Uriimport android.os.Buildimport android.os.Environmentimport android.os.Handlerimport android.util.DisplayMetricsimport android.util.Logimport android.widget.Toastimport androidx.annotation.RequiresApiimport com.blankj.utilcode.constant.PermissionConstantsimport com.blankj.utilcode.util.PermissionUtilsimport java.io.Fileimport java.lang.Exceptionimport java.nio.ByteBuffer@RequiresApi(Build.VERSION_CODES.LOLLIPOP)class ScreenRecordHelper @JvmOverloads constructor(    private var activity: Activity,    private val listener: OnVideoRecordListener?,    private var savePath: String = Environment.getExternalStorageDirectory().absolutePath + File.separator            + "DCIM" + File.separator + "Camera",    private val saveName: String = "record_${System.currentTimeMillis()}") {    private val mediaProjectionManager by lazy { activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as? MediaProjectionManager }    private var mediaRecorder: MediaRecorder? = null    private var mediaProjection: MediaProjection? = null    private var virtualDisplay: VirtualDisplay? = null    private val displayMetrics by lazy { DisplayMetrics() }    private var saveFile: File? = null    var isRecording = false    var recordAudio = false    init {        activity.windowManager.defaultDisplay.getMetrics(displayMetrics)    }    companion object {        private const val VIDEO_FRAME_RATE = 30        private const val REQUEST_CODE = 1024        private const val TAG = "ScreenRecordHelper"    }    fun startRecord() {        if (mediaProjectionManager == null) {            Log.d(TAG, "mediaProjectionManager == null,当前手机暂不支持录屏")            showToast(R.string.phone_not_support_screen_record)            return        }        PermissionUtils.permission(PermissionConstants.STORAGE, PermissionConstants.MICROPHONE)            .callback(object : PermissionUtils.SimpleCallback {                override fun onGranted() {                    mediaProjectionManager?.apply {                        listener?.onBeforeRecord()                        val intent = this.createScreenCaptureIntent()                        if (activity.packageManager.resolveActivity(                                intent,                                PackageManager.MATCH_DEFAULT_ONLY                            ) != null                        ) {                            activity.startActivityForResult(intent, REQUEST_CODE)                        } else {                            showToast(R.string.phone_not_support_screen_record)                        }                    }                }                override fun onDenied() {                    showToast(R.string.permission_denied)                }            }).request()    }    @RequiresApi(Build.VERSION_CODES.N)    fun resume() {        mediaRecorder?.resume()    }    @RequiresApi(Build.VERSION_CODES.N)    fun pause() {        mediaRecorder?.pause()    }    fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {        if (requestCode == REQUEST_CODE) {            if (resultCode == Activity.RESULT_OK) {                mediaProjection = mediaProjectionManager!!.getMediaProjection(resultCode, data)                // 部分手机录制视频的时候 会出现弹框                Handler().postDelayed({                    if (initRecorder()) {                        isRecording = true                        mediaRecorder?.start()                        listener?.onStartRecord()                    } else {                        showToast(R.string.phone_not_support_screen_record)                    }                }, 150)            } else {                showToast(R.string.phone_not_support_screen_record)            }        }    }    fun cancelRecord(){        stopRecord()        saveFile?.delete()        saveFile = null        listener?.onCancelRecord()    }    fun stopRecord(videoDuration: Long = 0, audioDuration: Long = 0, afdd: AssetFileDescriptor? = null){        stop()        if (audioDuration != 0L && afdd != null) {            syntheticAudio(videoDuration, audioDuration, afdd)        } else {            // saveFile            if (saveFile != null) {                val newFile = File(savePath, "$saveName.mp4")                // 录制结束后修改后缀为 mp4                saveFile!!.renameTo(newFile)                refreshVideo(newFile)            }            saveFile = null        }    }    private fun refreshVideo(newFile: File) {        Log.d(TAG, "screen record end,file length:${newFile.length()}.")        if (newFile.length() > 5000) {            val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)            intent.data = Uri.fromFile(newFile)            activity.sendBroadcast(intent)            Log.e("TAG","refreshVideo: "+savePath)            showToast(R.string.save_to_album_success)        } else {            newFile.delete()            showToast(R.string.phone_not_support_screen_record)            Log.d(TAG, activity.getString(R.string.record_faild))        }    }    private fun stop() {        if (isRecording) {            isRecording = false            try {                mediaRecorder?.apply {                    setOnErrorListener(null)                    setOnInfoListener(null)                    setPreviewDisplay(null)                    stop()                    Log.d(TAG, "stop success")                }            } catch (e: Exception) {                Log.e(TAG, "stopRecorder() error!${e.message}")            } finally {                mediaRecorder?.reset()                virtualDisplay?.release()                mediaProjection?.stop()                listener?.onEndRecord()            }        }    }    private fun initRecorder(): Boolean {        var result = true        val f = File(savePath)        if (!f.exists()) {            f.mkdir()        }        saveFile = File(savePath, "$saveName.tmp")        saveFile?.apply {            if (exists()) {                delete()            }        }        mediaRecorder = MediaRecorder()        val width = Math.min(displayMetrics.widthPixels, 1080)        val height = Math.min(displayMetrics.heightPixels, 1920)        mediaRecorder?.apply {            if (recordAudio) {                setAudioSource(MediaRecorder.AudioSource.MIC)            }            setVideoSource(MediaRecorder.VideoSource.SURFACE)            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)            setVideoEncoder(MediaRecorder.VideoEncoder.H264)            if (recordAudio) {                setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)            }            setOutputFile(saveFile!!.absolutePath)            setVideoSize(width, height)            setVideoEncodingBitRate(8388608)            setVideoFrameRate(VIDEO_FRAME_RATE)            try {                prepare()                virtualDisplay = mediaProjection?.createVirtualDisplay(                    "MainScreen", width, height, displayMetrics.densityDpi,                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, null, null                )                Log.d(TAG, "initRecorder 成功")            } catch (e: Exception) {                Log.e(TAG, "IllegalStateException preparing MediaRecorder: ${e.message}")                e.printStackTrace()                result = false            }        }        return result    }    private fun showToast(resId: Int) {        Toast.makeText(activity.applicationContext, activity.applicationContext.getString(resId), Toast.LENGTH_SHORT)            .show()    }    fun clearAll() {        mediaRecorder?.release()        mediaRecorder = null        virtualDisplay?.release()        virtualDisplay = null        mediaProjection?.stop()        mediaProjection = null    }    /**     * https://stackoverflow.com/questions/31572067/android-how-to-mux-audio-file-and-video-file     */    private fun syntheticAudio(audioDuration: Long, videoDuration: Long, afdd: AssetFileDescriptor) {        Log.d(TAG, "start syntheticAudio")        val newFile = File(savePath, "$saveName.mp4")        if (newFile.exists()) {            newFile.delete()        }        try {            newFile.createNewFile()            val videoExtractor = MediaExtractor()            videoExtractor.setDataSource(saveFile!!.absolutePath)            val audioExtractor = MediaExtractor()            afdd.apply {                audioExtractor.setDataSource(fileDescriptor, startOffset, length * videoDuration / audioDuration)            }            val muxer = MediaMuxer(newFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)            videoExtractor.selectTrack(0)            val videoFormat = videoExtractor.getTrackFormat(0)            val videoTrack = muxer.addTrack(videoFormat)            audioExtractor.selectTrack(0)            val audioFormat = audioExtractor.getTrackFormat(0)            val audioTrack = muxer.addTrack(audioFormat)            var sawEOS = false            var frameCount = 0            val offset = 100            val sampleSize = 1000 * 1024            val videoBuf = ByteBuffer.allocate(sampleSize)            val audioBuf = ByteBuffer.allocate(sampleSize)            val videoBufferInfo = MediaCodec.BufferInfo()            val audioBufferInfo = MediaCodec.BufferInfo()            videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)            audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)            muxer.start()            // 每秒多少帧            // 实测 OPPO R9em 垃圾手机,拿出来的没有 MediaFormat.KEY_FRAME_RATE            val frameRate = if (videoFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {                videoFormat.getInteger(MediaFormat.KEY_FRAME_RATE)            } else {                31            }            // 得出平均每一帧间隔多少微妙            val videoSampleTime = 1000 * 1000 / frameRate            while (!sawEOS) {                videoBufferInfo.offset = offset                videoBufferInfo.size = videoExtractor.readSampleData(videoBuf, offset)                if (videoBufferInfo.size < 0) {                    sawEOS = true                    videoBufferInfo.size = 0                } else {                    videoBufferInfo.presentationTimeUs += videoSampleTime                    videoBufferInfo.flags = videoExtractor.sampleFlags                    muxer.writeSampleData(videoTrack, videoBuf, videoBufferInfo)                    videoExtractor.advance()                    frameCount++                }            }            var sawEOS2 = false            var frameCount2 = 0            while (!sawEOS2) {                frameCount2++                audioBufferInfo.offset = offset                audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, offset)                if (audioBufferInfo.size < 0) {                    sawEOS2 = true                    audioBufferInfo.size = 0                } else {                    audioBufferInfo.presentationTimeUs = audioExtractor.sampleTime                    audioBufferInfo.flags = audioExtractor.sampleFlags                    muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo)                    audioExtractor.advance()                }            }            muxer.stop()            muxer.release()            videoExtractor.release()            audioExtractor.release()            // 删除无声视频文件            saveFile?.delete()        } catch (e: Exception) {            Log.e(TAG, "Mixer Error:${e.message}")            // 视频添加音频合成失败,直接保存视频            saveFile?.renameTo(newFile)        } finally {            afdd.close()            Handler().post {                refreshVideo(newFile)                saveFile = null            }        }    }    interface OnVideoRecordListener {        /**         * 录制开始时隐藏不必要的UI         */        fun onBeforeRecord()        /**         * 开始录制         */        fun onStartRecord()        /**         * 取消录制         */        fun onCancelRecord()        /**         * 结束录制         */        fun onEndRecord()    }}

6.布局

<?xml version="1.0" encoding="utf-8"?>    

end

更多相关文章

  1. android 播放音频和视频
  2. Android查看手机通讯录(ListView)
  3. JS判断当前环境为微信,手机判断浏览器类型
  4. android edittext 输入手机号码格式变化
  5. Android与服务器传递数据
  6. Android手机中紧急号码的定制
  7. Android简单UI界面的XML实现
  8. Android(安卓)调试工具集合
  9. Android一次刷机

随机推荐

  1. android onCreate中获取view宽高为0的多
  2. 在 Android(安卓)11 及更高版本系统中处
  3. Android集成ShareSDK第三方分享和登录
  4. Android之自定义最简单的竖向引导页
  5. 自定义React Native Modal,支持全屏弹框
  6. 安卓64位计算的转变
  7. ADB Interface驱动安装[Android(安卓)Stu
  8. Android使用Gallery实现照片拖动的特效
  9. 5张图片解决android studio报错:Failed to
  10. Android(安卓)stdio笔记