Android 原生 Picture in Picture 画中画功能避坑指南

最近生活有些变动所以断更好久,不过虽迟到但永远不会缺席。ChatGPT 浪潮还在持续扩大,各位同学一定要体验体验丫~

这篇主要介绍最近需求中遇到的问题,希望能帮助后来者少踩坑。先说结论:Android 原生画中画功能并不完善,如果可以接受 APP 有两个任务栈则可以使用;否则趁早自己用浮窗自定义实现画中画的功能吧。

1. PiP 简介

Android PiP 模式也称之为画中画模式,允许用户在使用应用程序的同时,在屏幕的一角或一侧浮动显示另一个应用程序或视频。这使得用户可以同时进行多项任务,而不必切换应用程序或中断正在进行的任务。如下所示:

图 1 PiP示例
(注:B站的 PiP 是自定义实现的,未使用系统 PiP)

2. 准备工作,跑通 Demo

官方文档:https://developer.android.google.cn/guide/topics/ui/picture-in-picture?hl=zh-cn
官方Demo:https://github.com/android/media-samples/tree/main/PictureInPictureKotlin

打开官方 Demo,首先得改一下 minSdkVersion,demo 里设置的是 API 31(Android 12.0),不满足实际应用需求,这里改为 23(Android 6.0). 但 PiP 功能只能在 Android8.0 及以上的系统上使用,所以用到一些方法时,需要注明 @RequiresApi(Build.VERSION_CODES.O)。所以,如果需要在 Android 8.0 以下的设备支持 PiP,只能使用自定义悬浮窗实现

还需要注释掉 setAutoEnterEnabled(true)setSeamlessResizeEnabled(false) 这两个方法。因为它们只能在 Android 12.0 及以上系统使用,且对于 PiP 的主体功能没有影响。setAutoEnterEnabled 用于设置 Activity 在退到后台时是否自动进入 PiP 模式,当设置为 true,则在用户点击 Home 键回到主屏幕时,Activity 可自动进入 PiP 模式,而不用开发者手动调用 enterPictureInPictureMode 方法;setSeamlessResizeEnabled 用于设置非视频画中画时的动画效果,不影响功能。

按照上述的内容设置完后就可以将 Demo 跑通了。

3. 示例代码分析

仅分析查看了 Demo 中的 MovieActivity 中的 PiP 相关的代码。比较重要的代码如下:

// code 1@RequiresApi(Build.VERSION_CODES.O)private fun minimize() {enterPictureInPictureMode(updatePictureInPictureParams())}

调用 enterPictureInPictureMode(@NonNull PictureInPictureParams params) 方法就可以进入 PiP,声明如下:

// code 2public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) { ···}

方法简介:它是 Activity 类中的方法,需要传递一个 PictureInPictureParams 类型对象。当系统成功将该 Activity 切换到 PiP 模式或已经处于 PiP,则返回值为 true;如果设备不支持 PiP 则返回 false。

再来看下构建 PictureInPictureParams 类型对象的 updatePictureInPictureParams() 方法:

// code 3
@RequiresApi(Build.VERSION_CODES.O)private fun updatePictureInPictureParams(): PictureInPictureParams {// 1、计算出 PiP 小窗的宽高比,这里直接使用播放视频的控件宽和高计算val aspectRatio = Rational(binding.movie.width, binding.movie.height)// 2、将播放视频的控件binding.movie设置为 PiP 中要展示的部分val visibleRect = Rect()binding.movie.getGlobalVisibleRect(visibleRect)val params = PictureInPictureParams.Builder().setAspectRatio(aspectRatio)// 3、指定进入画中画的屏幕部分。系统根据这个可实现平滑动画效果。这里就把之前生成的 visibleRect 传值过去.setSourceRectHint(visibleRect).build()setPictureInPictureParams(params)return params}

updatePictureInPictureParams 方法作用是构建出进入 PiP 的一些参数,比如进入小窗的控件,小窗的宽高比等。注释很清楚,源码直接拿来套用就行。需要注意的点:只能指定 PiP 模式的宽高比,并不能直接设置宽和高的具体值,系统会根据设置的宽高比自己计算具体值。

如果在播放器控件上层有其他的操作按钮等,还需要在 onPictureInPictureModeChanged 回调中进行处理,即进入 PiP 后隐藏这些按钮;退出后恢复这些按钮的状态。 如下是 Demo 中的实现:

// code 4override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)if (isInPictureInPictureMode) {// Hide the controls in picture-in-picture mode.binding.movie.hideControls()} else {// Show the video controls if the video is not playingif (!binding.movie.isPlaying) {binding.movie.showControls()}}}

通过这个方法可以监听 PiP 的进入和退出。

还有一些是 PiP 模式下的播放/暂停、上一个/下一个 操作按钮,即下图红框中的这三个按钮,相关的使用方式 Demo 中已有示例,这里不再赘述。
图 1 PiP 按钮

除此之外,还要在需要进入 PiP 的 Activity 的 AndroidManifest 中设置支持 PiP 的属性以及处理布局配置更改。这样一来,如果在 PiP 模式转换期间出现布局更改,该 Activity 就不会重新启动。

// code 5
<activity android:name="VideoActivity"android:supportsPictureInPicture="true"android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"...

这些官方文档中就有,这里也不再多说。

4. 功能实现及踩坑汇总

4.1 实现点击 Back 键及 Home 键自动进入 PiP

用户在观看视频时,点击返回键或 Home 键,当前 Activity 需要进入 PiP 继续播放,这是个常见的功能,实现起来也比较简单:

// code 6// 实现点击返回键进入 PiP@RequiresApi(Build.VERSION_CODES.O)override fun onBackPressed() {enterPictureInPictureMode(updatePictureInPictureParams())}// 实现点击 Home 键进入 PiP@RequiresApi(Build.VERSION_CODES.O)override fun onUserLeaveHint() {super.onUserLeaveHint()enterPictureInPictureMode(updatePictureInPictureParams())}

如果设置了之前提到的 setAutoEnterEnabled(true) 方法,则可以不用在 onUserLeaveHint() 回调里主动调用 enterPictureInPictureMode 方法进入 PiP。但建议还是不用 setAutoEnterEnabled,因为它只能在 Android 12 上使用。。。

onUserLeaveHint() 方法也是 Activity 中的方法,当 Activity 进入后台时就会调用它,比如用户点击 Home 键就会回调它。但有来电时,来电的 Activity 会自动带到前台,这时被退到后台的 Activity 的 onUserLeaveHint 方法并不会被调用。onUserLeaveHint 的调用时机是在 onPause 方法之前,这点需要注意。

4.2 实现 Activity 处于 PiP 时再次进入更新视频

假设 MovieActivity 已处于 PiP 并正在播放视频,用户点击另外一个视频又要跳转到 MovieActivity 的情形。如果不进行处理就会出现有两个 MovieActivity 同时播放视频的情况,即小窗播放的同时,还有一个另一个 MovieActivity 也在播放。如下所示,本来只有一个 PiP 在播放视频,然后点击 WATCH VIDEO TWO 按钮又进入了 MovieActivity,此时有两个视频同时在播放:

图 2

查看堆栈信息确实有两个 MovieActivity:
图 3

这种情况下是需要将 MovieActivity 由 PiP 恢复到正常状态并播放新的视频,如果视频内容没有变则接着播放原视频。官方 Demo 也有说明如何处理,需要两个步骤:
1)将 MovieActivity 的 launchMode 设置为 singleTask
2)在 MovieActivity 的 onNewIntent 方法里处理更新数据等逻辑;

比如我在打开 MovieActivity 时通过 Intent 传递不同的 video 来播放不同的视频,那么在 onNewIntent 中就需要接收传递的参数并更新:

// code 7
// MainActivity.kt    通过 Intent 传入不同的视频binding.btnWatchVid1.setOnClickListener {val intent = Intent(this, MovieActivity::class.java)intent.putExtra(MovieActivity.KEY_VIDEO_ID, R.raw.vid_bigbuckbunny)startActivity(intent)}binding.btnWatchVid2.setOnClickListener {val intent = Intent(this, MovieActivity::class.java)intent.putExtra(MovieActivity.KEY_VIDEO_ID, R.raw.vid_dajiang)startActivity(intent)}
// code 8
// MovieActivity.kt    onNewIntent 接收并更新override fun onNewIntent(intent: Intent?) {super.onNewIntent(intent)val newVideoId = intent?.getIntExtra(KEY_VIDEO_ID, R.raw.vid_bigbuckbunny)newVideoId?.let {// 更新视频binding.movie.setVideoResourceId(it)}}

在实际中可能更加复杂,但大体思路是一致的。

4.3 实现跳转其他 Activity 时,当前 Activity 自动进入 PiP

场景:正在 MovieActivity 里播放视频,用户点击某个按钮跳转到其他 Activity,MovieActivity 此时需进入 PiP,用户可以在新打开的 Activity 页面进行操作。

官方文档里并没有针对这一场景进行说明和提示,所以一开始以为很简单,直接跟之前一样调用 enterPictureInPictureMode(updatePictureInPictureParams()) 不就可以了么?于是就有了下面的代码:

// code 9binding.btnJumpTestOne.setOnClickListener {enterPictureInPictureMode(updatePictureInPictureParams())startActivity(Intent(this@MovieActivity, TestOneActivity::class.java))}

想着先将当前的 MovieActivity 进入 PiP,再跳转到其他的 Activity,结果 MovieActivity 直接退出了,也没有错误信息。看栈信息发现其实要跳转的新 Activity —— TestOneActivity 已经打开了。。。

打开失败的动图:

图 4 跳转失败示例1
在 MovieActivity 中点击 JUMP TO TESTONEACTIVITY 按钮跳转之后,堆栈信息如下,可以看到 pid = 21126 的进程就是 Demo 程序,TestOneActivity 确实打开了,MovieActivity 已退出:
图 5

加延时再试:

// code 10binding.btnJumpTestOne.setOnClickListener {lifecycleScope.launch {enterPictureInPictureMode(updatePictureInPictureParams())delay(1000)startActivity(Intent(this@MovieActivity, TestOneActivity::class.java))}}

确实进入 PiP 了,但后面跳转的 TestOneActivity 也在 PiP 了。。

图 6 跳转失败示例2

如果是先跳转 TestOneActivity 再进入 PiP ,经测试只会跳转并不会进入 PiP,这里就不再展示了。

经分析和实践发现,只能先进入 PiP 再进行跳转,之所以会出现在 PiP 里跳转,是因为后面跳转的 TestOneActivity 进入了 MovieActivity 所在的任务栈。Activity 在没有设置 taskAffinity 属性时,都会放在默认的同一个任务栈中。

所以想到的第一个方法就是,修改 MovieActivity 的 launchMode,改为 singleInstance。这样既可以保证任务栈中只有一个 MovieActivity 的实例,也可以将 MovieActivity 放在独立的任务栈中。试了下果然可以了,但会在多任务切换页里出现同一个 App 有两个任务栈的现象:

图7 一个 App 出现多个任务栈

这是第一个问题,这个问题直到最后也无法解决,在 AndroidManifest 文件中添加 autoRemoveFromRecentsexcludeFromRecents 都没用,还是会在多任务切换页出现两个栈。

还有一个问题即问题二,还是 singleInstance 引起的。当 MovieActivity 正在以非小窗模式播放视频时,先进入多任务切换页,再按 Home 键回到主屏幕,然后再点击 App 图标进入时,发现进入的不是 MovieActivity,而是 MovieActivity 的上个页面,即 MainActivity,此时再进入多任务切换页面,会发现 MovieActivity 所在那个任务栈已经消失了。这里其实有两个问题:
1)回到主屏幕后再点击 App 图标应该回到 MovieActivity;
2)用户并没有关闭 MovieActivity,但进入多任务切换页面后无法找到 MovieActivity 了。如下动图:
图 8 问题二

问题二的两个问题得先解决 2)才能解决 1)。2)之所以会出现是因为一个 App 出现了两个任务栈,这两个任务栈的 taskAffinity参数默认是一样的,一山不容二虎,那么点击桌面图标后,就会把之前的任务栈移到前台,然后会把另一个任务栈干掉。
所以首先要保留这两个任务栈,给 MovieActivity 设置一个单独的 taskAffinity名称,这就可以得以保留,问题 2)就解决了。只有先保留任务栈,才能解决问题 1)。

导致问题1)的原因是因为用户在点击 App 图标时,会将 MainActivity 所在的栈移到前台,那么首先可以想到的方法是,在点击 App 图标时,将含有 MovieActivity 的栈移到前台显示。所以我们可以注册一个生命周期监听,在 onResume 时,去遍历 App 的所有任务栈,找到含有 MovieActivity 的栈并将其移到前台即可:

// code 11   DemoApplication.kt  onCreate方法中
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks{···override fun onActivityResumed(activity: Activity) {val appCompatActivity = if (activity is AppCompatActivity) {activity} else {return}// 限制条件:所有的 activity 必须为 AppCompatActivity 或其子类val activityManager = appCompatActivity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManagerfor (i in activityManager.appTasks.indices) {val appTask = activityManager.appTasks[i]val taskInfo = appTask.taskInfoif (taskInfo.topActivity == null) continueval topActivityName = taskInfo.topActivity?.classNameif ((!topActivityName.isNullOrBlank() && topActivityName.contains(MovieActivity::class.java.simpleName)) && i != 0) {// 如果存在视频播放页且所在的 Task 不在前台,则需要将其移到前台activityManager.moveTaskToFront(taskInfo.id,ActivityManager.MOVE_TASK_NO_USER_ACTION)}}}···
})

很明显这个方法并不好,App 中每个 Activity 在调用 onResume 时都会走一遍这个逻辑;且 App 中所有的 Activity 必须为 AppCompatActivity 或它的子类。还得需要申请 REORDER_TASKS 权限:

// code 12   AndroidManifest.xml
<!--  申请可排序任务栈权限  -->
<uses-permission android:name="android.permission.REORDER_TASKS" />

并且这里还遇到一个问题:当在 MovieActivity 跳转到 TestOneActivity 时,进入 PiP,此时点击 PiP 中的关闭按钮关闭 PiP,然后点击 Home 回到桌面,再点击 App 图标会发现进入的是 MovieActivity 页,而并不是 TestOneActivity:
图 9 点击App进入页面不对的问题

经分析,原因是 PiP 的关闭按钮点击后,只是将 MovieActivity 退到了后台,并没有销毁。。。所以退到后台,再点击 App 图标时,会将包含 MovieActivity 的任务栈显示到前台,而不显示 TestOneActivity 所在的任务栈。那么我们就需要在关闭 PiP 按钮的回调中直接关闭 Activity,但我们开发者拿不到关闭按钮的回调,所以就有了下面的问题:

如何在用户点击 PiP 里的关闭按钮时,关闭 PiP 所在的 Activity?
经多次实验得知,PiP 虽然没有关闭小窗的回调,但会先调用 onStop 然后会调用 onPictureInPictureModeChanged 方法。所以可以根据是否回调了 onStop 来间接判断是否点击了 PiP 小窗里的关闭按钮。

// code 13
// MovieActivity 的 ViewModel
class MovieViewModel: ViewModel() {//进入或退出画中画模式所在Activity的事件 true: 进入; false: 退出val enterOrExitPiPMode = MutableLiveData<Boolean>()
}// MovieActivity.kt
@RequiresApi(Build.VERSION_CODES.O)
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration
) {super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)if (isInPictureInPictureMode) {···} else {···// PiP没有关闭小窗的回调,但会先回调 onStop 然后回调 onPictureInPictureModeChanged 方法。可以根据// 是否回调了 onStop 来间接判断是否点击了PiP小窗里的关闭按钮,这里需要在用户主动关闭小窗后 finish 掉// MovieActivityif (lifecycle.currentState < Lifecycle.State.STARTED) {movieViewModel.enterOrExitPiPMode.value = false}}
}// MovieActivity.kt
// 进入 or 退出画中画模式
movieViewModel.enterOrExitPiPMode.observe(this) {if (it) {// 这里暂没有操作} else {// 关闭画中画模式事件,需要直接 finish 掉 MovieActivityfinish()}
}

这里使用 LiveData 是因为在其他的页面可能也需要关闭 PiP,所以可以先获得 ViewModel,通过更新 enterOrExitPiPMode 的值去关闭 PiP。

4.4 去掉 PiP 下方自带的三个按钮

PiP 小窗上的 6个按钮只有底部的三个按钮可自定义,另外的三个按钮无法修改,这也是为什么无法拿到关闭按钮回调的原因。如果需要针对底部的三个按钮进行自定义,通过设置 PictureInPictureParams 参数实现,但最多只能自定义 3个,我们这里不需要这三个按钮,就可以设置一个透明按钮间接去掉:

// code 14
// 第一步:新建一个 RemoteAction list
@RequiresApi(Build.VERSION_CODES.O)
private fun initPiPActions(): List<RemoteAction> {//去掉原生小窗中默认自带的 上一个、暂停、下一个 三个按钮val actions = mutableListOf<RemoteAction>()val emptyIntent = PendingIntent.getBroadcast(requireContext(), 0, Intent(), PendingIntent.FLAG_IMMUTABLE)actions.add(RemoteAction(Icon.createWithResource(requireContext(), R.drawable.divider_transparent), "", "", emptyIntent))return actions
}// 第二步:设置到 PictureInPictureParams 参数中
val params = PictureInPictureParams.Builder().setAspectRatio(aspectRatio)// Specify the portion of the screen that turns into the picture-in-picture mode.// This makes the transition animation smoother..setSourceRectHint(visibleRect).setActions(initPiPActions()).build()

自定义底部三个按钮的方法有两种:一是通过实现 RemoteAction 的方法;二是官方 Demo 中的方法。关于这个内容参考文献2 更加详实,可以借鉴。

5. 难以解决的问题

以上的坑基本趟完了,但下面的坑实在是难以解决,这里也欢迎大佬们能给出建议。

5.1 App 出现两个任务栈

为了实现从 MovieActivity 跳转到其他 Activity 时,MovieActivity 自身进入 PiP,必须将 MovieActivity 放到独立的任务栈中,所以就会出现这个问题。以上文中也有说明。

5.2 PiP 模式下跳转一个 singleTask 的 Activity 会在 PiP 中跳转

官方 Demo 中将 MovieActivity 的 launchMode 设置为 singleTask 且不设置 taskAffinity 时,当 MovieActivity 正处于 PiP 模式下,跳转到另一个 Activity 时,目标 Activity 的 launchMode 不能为 singleTask,否则目标 Activity 会在 PiP 中跳转。

图10 这里将 TestOneActivity 的 launchMode 设置为 singleTask,然后从 MovieActivity 跳到 TestOneActivity 时,TestOneActivity 出现在了 PiP 中。而通常项目中会有许多 Activity 的 launchMode 设置为了 singleTask,所以原生 PiP 方案最终被否。。。
github 上也有 issue:https://github.com/android/media-samples/issues/85

6. 小知识点汇总

6.1 ActivityManager.MOVE_TASK_NO_USER_ACTION 的作用

常用于 activityManager.moveTaskToFront 方法中,意思是不把当前的操作看作是用户触发的行为,即不会调用当前 Activity 的 onUserLeaveHint 方法。还有一个是 ActivityManager.MOVE_TASK_WITH_HOME ,这个就会调用当前 Activity 的 onUserLeaveHint 方法。实际应用中貌似很少用到。

6.2 autoRemoveFromRecents 和 excludeFromRecents 的用法

6.2.1 android:autoRemoveFromRecents 用法

android:autoRemoveFromRecents 是在任务栈中的最后一个 Activity 完成之前,由具有此属性的 Activity 启动的任务栈是否保留在多任务切换页面中。即 autoRemoveFromRecents 指定了当 Activity 被系统回收时,是否保留在多任务切换页面中。默认值为 false。

当设置为 true 时: 当 Activity 被系统回收时,从最近使用的多任务切换页中移除该 Activity 所在的任务栈;当设置为 false 时: 当 Activity 被系统回收时,不从最近使用的多任务切换页中移除该 Activity 所在的任务栈。

这个属性主要用于:
1)一些临时 Activity,当它们被销毁后,不希望它们出现在多任务切换页中,可以设置为 true;
2)一些没有重要数据的 Activity,如果设置为 true,当内存不足被系统回收后,由于它已经从多任务切换页移除,用户不太可能再去恢复它,及时移除有利于内存回收;
3)一些包含敏感数据的 Activity,为了安全考虑,不希望它出现在多任务切换页中,可以设置为 true。
所以,总体来说,这个属性主要是出于内存管理和安全考虑,控制 Activity 在被系统回收后是否从多任务切换页中移除。

6.2.2 android:excludeFromRecents 用法

android:excludeFromRecents 也是一个 Activity 属性,它指定了是否从多任务切换页中排除该 Activity 所在的任务栈。默认值为 false。

当设置为 true 时:该 Activity 所在的任务栈不会出现在多任务切换页中;当设置为 false 时:不从多任务切换页中排除该 Activity 所在的任务栈。

这个属性与 android:autoRemoveFromRecents 很像,它们的区别是:
android:autoRemoveFromRecents 是当 Activity 被系统回收时,所在栈是否从多任务切换页中移除;
android:excludeFromRecents 是 Activity 所在的栈从一开始就不会出现在任务列表中。

更多内容,欢迎关注公众号:修之竹
或者查看 修之竹的 Android 专辑

赞人玫瑰,手留余香!欢迎点赞、转发~ 转发请注明出处~

参考文献

  1. 官方文档:https://developer.android.google.cn/guide/topics/ui/picture-in-picture?hl=zh-cn
  2. 总结系列-Android画中画模式-看这篇就够啦; ZhangQiang-; https://blog.csdn.net/u011200604/article/details/104701266
  3. Android 面试黑洞——当我按下 Home 键再切回来,会发生什么?; 扔物线朱凯; https://www.bilibili.com/video/BV1CA41177Se/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/19592.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

老胡的周刊(第090期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 &#x1f3af; 项目 privateGPT[2] 为保证数据私密性&#xff0c…

沉浸式翻译 – 沉浸式的阅读与学习双语的翻译工具

为何称为沉浸式呢 智能识别网页主内容区进行翻译&#xff0c;与同类插件翻译整个网页不同&#xff0c;降低对原网页的“侵入性”&#xff0c;提升阅读体验&#xff0c;因此得名“沉浸式翻译”。 受宠的缘由 自从谷歌翻译不再对大陆用户提供服务后。内心是一阵酸痛的&#xf…

如何让你的程序有背景音乐

1.先将MP3格式转化为wav,只能播放wav格式。&#xff08;下载格式工厂即可转换&#xff09; 2.调用函数PlaySound, 第一个参数&#xff1a;路径 第二个参数&#xff1a;窗口&#xff0c;NULL为全部窗口 第三个参数&#xff1a; 打开方式 PlaySound("D:\\dream.mp3",…

Unity背景音乐控制

实现功能&#xff1a; 点击按钮可实现音乐的播放与暂停及再次播放等&#xff0c;同时按钮sprite图片可在播放与暂停中切换。如点击按钮&#xff0c;图片变为暂停&#xff0c;同时播放背景音乐&#xff0c;再次点击按钮&#xff0c;即点击暂停按钮&#xff0c;音乐停止播放&…

android开发之背景音乐与音效

文章转载于https://www.cnblogs.com/hesi/p/5750862.html 仅供博主笔记学习用 android开发之背景音乐与音效 一&#xff1a;添加背景音乐&#xff08;MediaPlayer&#xff09; MediaPlayer class can be used to control playback of audio/video files and streams.MediaP…

在程序中添加背景音乐!...

//呵呵&#xff0c;这还是我第一次实现该功能呢&#xff0c;效果不错哦! //在程序中使用背景音乐时&#xff0c;记得包括以下两行代码(使用PlaySound函数 //时需要在#include<windows.h>后面加上, //注意&#xff1a;不能加在前面) //#include <mmsystem.h>    …

小程序之背景音乐—wx.backgroundAudioManager

onMusicTap:function(){const backgroundAudioManager wx.getBackgroundAudioManager()backgroundAudioManager.title 此时此刻;backgroundAudioManager.epname 此时此刻;backgroundAudioManager.singer 许巍;backgroundAudioManager.coverImgUrl http://y.gtimg.cn/music…

Unity(8)-开启或关闭背景音乐

文章目录 前言其他介绍上一篇笔记下一篇笔记 一、项目结构二、脚本[1]. 获取音频组件[2]. 按键监听[3]. 判断播放状态[4]. 开启或关闭音频[5]. 全部代码 三、创建Audio Source 前言 音乐播放时按下指定按键关闭音乐&#xff0c;未播放时按下按键开启音乐。 通过激活和关闭Autdi…

Android添加背景音乐

添加背景音乐 1、新建类MusicServer package com.example.happy; import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.IBinder; public class MusicServer extends Service { private MediaPlayer me…

安卓studio 添加后台bgm音乐的几种方法

在app内播放后台音频需要调用Service组件 Service组件需要在清单文件里使用标签注册 &#xff08;一般会自动在文件里注册&#xff09; Service 是编写服务组件的抽象基类 onCreate() 和onDestroy() 是Service的2个重要生命周期方法&#xff0c;需要在其内编写代码 非绑定方…

Unity3D 添加背景音乐及按键音乐

Unity3D中添加背景音乐 在制作Unity3D游戏中 缺少BGM的游戏是乏味的 如何在Unity游戏中添加背景音乐呢 因为我经常用的格式是 2 by 3 所以可能会有不一样的步骤请自己寻找 首先选中 Main Camera主相机 在Inspector中 点击Add Component 搜索Audio Source 并点击添加 添加…

Android背景和音乐

Android背景和音乐 前言一、添加背景图片二、加入背景音乐 前言 简单的给app添加背景图和音乐 一、添加背景图片 准备好一张合适的背景图片新建一个Empty Activity项目选择Java语言在res中创建一个mipmap文件,将准备的图片粘贴到该文件目录下 在activity_main.xml里面添加代…

如何在unity中实现切换背景音乐

第一步&#xff1a;在添加好Audio Source插件后&#xff0c;添加你的第一首歌在AudioClip中 第二部&#xff1a;创建script脚本&#xff0c;创建 歌曲曲目控制C&#xff0c;歌曲组T1&#xff0c;插件名T 第三步&#xff1a;在updata中添加判定当前是否播放完毕&#xff0c;注&a…

创业日记

最近在拼命的写代码&#xff0c;估计这段时间写代码的行数等于过去一年的了&#xff0c;这是保守估计&#xff0c;如果不保守估计就不好说了&#xff0c;我本来想在原来的小组群里发话说&#xff0c;打算是下面这段对话 我&#xff1a;X总&#xff0c;你站起来 X总&#xff1a;…

【实战】感恩教师节小程序制作

0产品功能定位 一句话定位&#xff1a;祝福完全自定义的、微信一键分享的教师节感恩小程序、 本产品是基于wordpress网站搭建的一个感恩教师节小程序&#xff0c;适用于但不限于以下场景&#xff1a; 1、用于庆祝教师节&#xff0c;传达班级、学生对老师的感恩之情。并支持点…

ChatGPT别来沾边!好莱坞编剧拒绝“给AI打工”,集体罢工抗议

克雷西 发自 凹非寺量子位 | 公众号 QbitAI ChatGPT可能做梦也想不到&#xff0c;自己有一天会成为人类罢工的理由。 工作会不会被AI取代还未有定论&#xff0c;好莱坞的编剧们就抢先一步罢工了。 他们罢工的理由也不完全是担心失业&#xff0c;主要是AI创作让他们感觉受到了侮…

计算机网络利弊的作文英语作文,网络的弊端英语作文(精选6篇)

网络的弊端英语作文(精选6篇) 在平时的学习、工作或生活中&#xff0c;大家都经常接触到作文吧&#xff0c;借助作文可以提高我们的语言组织能力。一篇什么样的.作文才能称之为优秀作文呢&#xff1f;以下是小编为大家收集的网络的弊端英语作文(精选6篇)&#xff0c;欢迎大家分…

计算机网络的对学生的利弊英语作文,网络的利与弊英语作文范文

网络的利与弊英语作文范文 英语作文:网络的利与弊(The Advantage and Disadvantage of Internet) With the development of high technology, Internet is more and more popular. No matter children or the old know how to search the Internet. The emerging of the Inter…

使用计算机的利与弊,雅思大作文范文:计算机的利与弊

新东方在线雅思频道特为大家收集整理了雅思大作文范文&#xff1a;计算机的利与弊&#xff0c;供大家阅读参考。认真研读一定的雅思范文及作文模板可以帮助我们检验自己的写作水平&#xff0c;并能很好地吸收和应用优秀范文里的优秀内容~更多雅思报名官网的最新消息&#xff0c…

投资人哑火、创业者狂欢、孵化器饥渴,大模型下的大变革

划重点&#xff1a; 1、在生成式AI的浪潮中&#xff0c;创业者很激动&#xff0c;投资人却哑火了。生成式AI发展迅速&#xff0c;许多投资人根本来不及反应&#xff1b;此外&#xff0c;生成式AI也面临着两大难题&#xff0c;一个是底层生态的稳定性问题&#xff1b;另一个则是…