当前位置: 首页 > news >正文

文化类网站是不是休闲娱乐类网站wordpress有哪些小工具

文化类网站是不是休闲娱乐类网站,wordpress有哪些小工具,wordpress表格布局插件,网站栏目划分的原则前言 在我之前的文章 《以不同的形式在安卓中创建GIF动图》 中#xff0c;我挖了一个坑#xff0c;可以通过录制屏幕后转为 GIF 的方式来创建 GIF。只是当时我只是提了这么一个思路#xff0c;并没有给出录屏的方式#xff0c;所以本文的内容就是教大家如何通过调用系统 A…前言 在我之前的文章 《以不同的形式在安卓中创建GIF动图》 中我挖了一个坑可以通过录制屏幕后转为 GIF 的方式来创建 GIF。只是当时我只是提了这么一个思路并没有给出录屏的方式所以本文的内容就是教大家如何通过调用系统 API 的方式录制屏幕。 开始实现 技术原理 在安卓 5.0 之前我们是无法通过常规的方式来录制屏幕或者截图的要么只能 ROOT要么就是只能用一些很 Hack 的方式来实现。 不过在安卓 5.0 后安卓开放了 MediaProjectionManager 、 VirtualDisplay 等 API使得普通应用录屏成为了可能。 简单来说录屏的流程如下 拿到 MediaProjectionManager 对象通过 MediaProjectionManager.createScreenCaptureIntent() 拿到请求权限的 Intent 然后用这个 Intent 去请求权限并拿到一个权限许可令牌resultData本质上还是个 Intent。通过拿到的 resultData 创建 VirtualDisplay投影。VirtualDisplay 将图像数据渲染至 Surface 中最终我们可以将 Surface 的数据流写入并编码至视频文件。Surface 可以由 MediaCodec 创建而 MediaMuxer 可以将 MediaCodec 的数据编码至视频文件中 从上面的流程可以看出其实核心思想就是通过 VirtualDisplay 拿到当前屏幕的数据然后绕一圈将这个数据写入视频文件中。 而 VirtualDisplay 顾名思义其实是用来做虚拟屏幕或者说投影的但是这里并不妨碍我们通过它来录屏啊。 不过由于我们是通过虚拟屏幕来实现录屏的所以如果应用声明了禁止投屏或使用虚拟屏幕那么我们录制的内容将是空白的黑屏。 准备工作 明白了实现原理之后我们需要来做点准备工作。 首先是做好界面布局在主入口编写布局 override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {val context LocalContext.currentScreenRecordTheme {// A surface container using the background color from the themeSurface(modifier Modifier.fillMaxSize(),color MaterialTheme.colors.background) {Column(modifier Modifier.fillMaxSize(),verticalArrangement Arrangement.Center,horizontalAlignment Alignment.CenterHorizontally) {Button(onClick {startServer(context)}) {Text(text 启动)}}}}} }布局很简单就是居中显示一个启动按钮点击按钮后启动录屏服务Server这里因为我们的需求是需要录制所有应用界面而非本APP的界面所以需要使用一个前台服务并显示一个悬浮按钮用于控制录屏开始与结束。 所以我们需要添加悬浮窗权限并动态申请 添加权限 uses-permission android:nameandroid.permission.SYSTEM_ALERT_WINDOW / 检查并申请权限 if (Settings.canDrawOverlays(context)) {// ……// 已有权限 } else {// 跳转到系统设置手动授予权限这里其实可以直接跳转到当前 APP 的设置页面但是不同的定制 ROM 设置页面路径不一样需要适配所以我们直接跳转到系统通用设置让用户自己找去Toast.makeText(context, 请授予“显示在其他应用上层”权限后重试, Toast.LENGTH_LONG).show()val intent Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,Uri.parse(package:${context.packageName}))context.startActivity(intent) }悬浮界面权限拿到后就是申请投屏权限。 首先定义 Activity Result Api并在获取到权限后将 ResultData 传入 Server最后启动 Server private lateinit var requestMediaProjectionLauncher: ActivityResultLauncherIntentoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// ……requestMediaProjectionLauncher registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {if (it.resultCode Activity.RESULT_OK it.data ! null) {OverlayService.setData(it.data!!)startService(Intent(this, OverlayService::class.java))}else {Toast.makeText(this, 未授予权限, Toast.LENGTH_SHORT).show()}} }然后在按钮的点击回调中启动这个 Launcher val mediaProjectionManager getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager requestMediaProjectionLauncher.launch(mediaProjectionManager.createScreenCaptureIntent() )在这里我们通过 getSystemService 方法拿到了 MediaProjectionManager 并通过 mediaProjectionManager.createScreenCaptureIntent() 拿到请求权限的 Intent。 最终在授予权限后启动录屏 Server。 但是这里有一点需要特别注意由于安卓系统限制我们必须使用前台 Server 才能投屏并且还需要为这个前台 Server 显式设置一个通知用于指示 Server 正在运行中否则将会抛出异常。 所以添加前台服务权限 uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / 然后在我们的录屏服务中声明前台服务类型 serviceandroid:name.overlay.OverlayServiceandroid:enabledtrueandroid:exportedfalseandroid:foregroundServiceTypemediaProjection /最后我们需要为这个服务绑定并显示一个通知 private fun initRunningTipNotification() {val builder Notification.Builder(this, running)builder.setContentText(录屏运行中).setSmallIcon(R.drawable.ic_launcher_foreground)val notificationManager getSystemService(NOTIFICATION_SERVICE) as NotificationManagerval channel NotificationChannel(running,显示录屏状态,NotificationManager.IMPORTANCE_DEFAULT)notificationManager.createNotificationChannel(channel)builder.setChannelId(running)startForeground(100, builder.build()) }需要注意的是这里我们为了方便讲解直接将创建和显示通知都放到了点击悬浮按钮后并且停止录屏后也没有销毁通知。 各位在使用的时候需要根据自己需求改一下。 自此准备工作完成。 哦对了关于如何使用 Compose 显示悬浮界面因为不是本文重点而且我也是直接套大佬的模板所以这里就不做讲解了感兴趣的可以自己看源码。 下面开始讲解如何录屏。 开始录屏 首先我们编写了一个简单的帮助类 ScreenRecorder class ScreenRecorder(private var width: Int,private var height: Int,private val frameRate: Int,private val dpi: Int,private val mediaProjection: MediaProjection?,private val savePath: String ) {private var encoder: MediaCodec? nullprivate var surface: Surface? nullprivate var muxer: MediaMuxer? nullprivate var muxerStarted falseprivate var videoTrackIndex -1private val bufferInfo MediaCodec.BufferInfo()private var virtualDisplay: VirtualDisplay? nullprivate var isStop false/*** 停止录制* */fun stop() {isStop true}/*** 开始录制* */fun start() {try {prepareEncoder()muxer MediaMuxer(savePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)virtualDisplay mediaProjection!!.createVirtualDisplay($TAG-display,width,height,dpi,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,surface,null,null)recordVirtualDisplay()} finally {release()}}private fun recordVirtualDisplay() {while (!isStop) {val index encoder!!.dequeueOutputBuffer(bufferInfo, TIMEOUT_US.toLong())if (index MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {resetOutputFormat()} else if (index MediaCodec.INFO_TRY_AGAIN_LATER) {//Log.d(TAG, retrieving buffers time out!);//delay(10)} else if (index 0) {check(muxerStarted) { MediaMuxer dose not call addTrack(format) }encodeToVideoTrack(index)encoder!!.releaseOutputBuffer(index, false)}}}private fun encodeToVideoTrack(index: Int) {var encodedData encoder!!.getOutputBuffer(index)if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG ! 0) {bufferInfo.size 0}if (bufferInfo.size 0) {encodedData null}if (encodedData ! null) {encodedData.position(bufferInfo.offset)encodedData.limit(bufferInfo.offset bufferInfo.size)muxer!!.writeSampleData(videoTrackIndex, encodedData, bufferInfo)}}private fun resetOutputFormat() {check(!muxerStarted) { output format already changed! }val newFormat encoder!!.outputFormatvideoTrackIndex muxer!!.addTrack(newFormat)muxer!!.start()muxerStarted true}private fun prepareEncoder() {val format MediaFormat.createVideoFormat(MIME_TYPE, width, height)format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL)encoder MediaCodec.createEncoderByType(MIME_TYPE)encoder!!.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)surface encoder!!.createInputSurface()encoder!!.start()}private fun release() {if (encoder ! null) {encoder!!.stop()encoder!!.release()encoder null}if (virtualDisplay ! null) {virtualDisplay!!.release()}mediaProjection?.stop()if (muxer ! null) {muxer?.stop()muxer?.release()muxer null}}companion object {private const val TAG el, In ScreenRecorderprivate const val MIME_TYPE video/avc // H.264 Advanced Video Codingprivate const val IFRAME_INTERVAL 10 // 10 seconds between I-framesprivate const val BIT_RATE 6000000private const val TIMEOUT_US 10000} }在这个类中接收以下构造参数 width: Int, 创建虚拟屏幕以及写入的视频宽度height: Int, 创建虚拟屏幕以及写入的视频高度frameRate: Int, 写入的视频帧率dpi: Int, 创建虚拟屏幕的 DPImediaProjection: MediaProjection?, 用于创建虚拟屏幕的 mediaProjectionsavePath: String, 写入的视频文件路径 我们可以通过调用 start() 方法开始录屏调用 stop() 方法停止录屏。 调用 start() 后会首先调用 prepareEncoder() 方法。该方法主要用途是按照给定参数创建 MediaCodec 并通过 encoder!!.createInputSurface() 创建一个 Surface 以供后续接收虚拟屏幕的图像数据。 预先设置完成后按照给定路径创建 MediaMuxer将参数和之前创建的 surface 传入创建一个新的虚拟屏幕并开始接受图像数据。 最后循环从上面创建的 MediaCodec 中逐帧读出有效图像数据并写入 MediaMuxer 中即写入视频文件中。 看起来可能比较绕但是理清楚之后还是非常简单的。 接下来就是如何去调用这个帮助类。 在调用之前我们需要预先准备好需要的参数 val savePath File(externalCacheDir, ${System.currentTimeMillis()}.mp4).absolutePath val screenSize getScreenSize() val mediaProjection getMediaProjection()savePath 表示写入的视频文件路径这里我偷懒直接写成了 APP 的缓存目录如果想要导出到其他地方记得处理好运行时权限。screenSize 表示的是当前设备的屏幕尺寸mediaProjection 表示请求权限后获取到的权限“令牌” 在 getScreenSize() 中我获取了设备的屏幕分辨率 private fun getScreenSize(): IntSize {val windowManager getSystemService(WINDOW_SERVICE) as WindowManagerval screenHeight windowManager.currentWindowMetrics.bounds.height()val screenWidth windowManager.currentWindowMetrics.bounds.width()return IntSize(screenWidth, screenHeight) }但是如果我直接把这个分辨率传给帮助类创建 MediaCodec 的话会报错 java.lang.IllegalArgumentExceptionat android.media.MediaCodec.native_configure(Native Method)at android.media.MediaCodec.configure(MediaCodec.java:2214)at android.media.MediaCodec.configure(MediaCodec.java:2130)不过这个问题只在某些分辨率较高的设备上出现猜测是不支持高分辨率视频写入吧所以我实际上使用时是直接写死一个较小的分辨率而不是使用设备的分辨率。 然后在 getMediaProjection() 中我们通过申请到的权限令牌生成 MediaProjection private fun getMediaProjection(): MediaProjection? {if (resultData null) {Toast.makeText(this, 未初始化, Toast.LENGTH_SHORT).show()} else {try {val mediaProjectionManager getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManagerreturn mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, resultData!!)} catch (e: IllegalStateException) {Log.e(TAG, getMediaProjection: , e)Toast.makeText(this, ERR: ${e.stackTraceToString()}, Toast.LENGTH_LONG).show()}catch (e: NullPointerException) {Log.e(TAG, getMediaProjection: , e)}catch (tr: Throwable) {Log.e(TAG, getMediaProjection: , tr)Toast.makeText(this, ERR: ${tr.stackTraceToString()}, Toast.LENGTH_LONG).show()}}return null }最后通过上面生成的这两个参数初始化录屏帮助类然后调用 start() // 这里如果直接使用屏幕尺寸会报错 java.lang.IllegalArgumentException recorder ScreenRecorder(886, // screenSize.width,1920, // screenSize.height,24,1,mediaProjection,savePath )CoroutineScope(Dispatchers.IO).launch {try {recorder.start()} catch (tr: Throwable) {Log.e(TAG, startScreenRecorder: , tr)recorder.stop()withContext(Dispatchers.Main) {Toast.makeText(thisOverlayService, 录制失败, Toast.LENGTH_LONG).show()}} }这里我把开始录屏放到了协程中实际上由于我们的程序是运行在 Server 中所以并不是必须在协程中运行。 总结 自此在安卓中录屏的方法已经全部介绍完毕。 实际上同样的原理我们也可以用于实现截图。 截图和录屏不同的地方在于创建虚拟屏幕时改为使用 ImageReader 创建然后就可以从 ImageReader 获取到 Bitmap。 最后附上完整的 demo 地址 ScreenRecord
http://www.w-s-a.com/news/375311/

相关文章:

  • 微信公众号微网站建设专业网站建设出售
  • 怎么用wordpress建立自己的网站加强校园网站建设
  • 用什么做网站后台的织梦网站怎么上传
  • 怎么获取网站数据做统计百度快照推广有效果吗
  • 淘宝领卷网站什么做制造网站开发
  • 如何做com的网站网站建设投标书模板
  • 郑州网络营销网站优化网站技术方案怎么写
  • 济南市住房和城乡建设局网站wordpress mnews主题
  • ios开发网站app网站建设企业有哪些方面
  • 网站主页 优帮云深圳代做网站后台
  • app 与网站网站建设要做什么
  • 厦门国外网站建设公司郑州核酸点推vip服务
  • 免费网线seo外链怎么做
  • 宽带技术网网站wordpress widget hook
  • 山西省住房和城乡建设厅网站报名wordpress添加标签插件
  • 网站怎么自己做外贸网站案例
  • 做网站的优势公司网站怎么做站外链接
  • 海城网站制作建设精准营销的营销方式
  • 北京短视频拍摄公司重庆网站seo推广公司
  • 广州免费推广网站建设4399网页游戏大全
  • 网站的构架与组成建站公司兴田德润
  • php网站部署步骤邯郸哪有做网站的
  • 做设计什么设计比较好的网站南充市住房和城乡建设局考试网站
  • 郑州做系统集成的公司网站龙岩
  • 厦门SEO_厦门网站建设网络营销课程视频
  • vs 2015 网站开发开网店在线咨询
  • 前端如何优化网站性能大学学校类网站设计
  • 中国铁路建设投资公司网站熊学军中国it外包公司排名前50
  • 房产网站的建设广州推广排名
  • 湟源县网站建设wordpress删除未分类