我已经熟悉了使用Canvas在动态壁纸中绘制Bitmap,最终我还尝试了在动态壁纸中播放GIF动画(这里)和简单的视频播放(这里)
我想通过使用其他人制作的东西(将 OpenGL 与 ExoPlayer 一起使用)来提高它的效率,并且仍然可以正确播放视频,包括能够水平滚动它。
使用画布在这里不起作用,因为它用于简单绘图。使用我在视频中看到的类是行不通的,因为它们不能很好地控制移动和缩放,也不能显示其他内容(背景颜色/图像)。
遗憾的是,我发现我可以阅读的内容并不多,但我找到了一些可以帮助我的解决方案:
Muzei - 仅用于图像(具有一些效果)。效果很好,可以很好地水平滚动。然而,它是一个巨大的应用程序,很难理解那里发生了什么。似乎它单独使用OpenGL3。
alynx-live-wallpaper(我的更新,Kotlin fork here) - 可以显示视频并支持 OpenGL2 和 OpenGL3。我认为它在中间播放视频,但其水平滚动有问题(here)。存在的问题是您无法到达视频的边缘(左侧/右侧),即使您应该能够做到这一点。
LibGdx - 我几乎没有发现它的任何资源用于动态壁纸,但我确信它可以播放视频和图像。尝试后我在reddit上询问了它,这里。
我决定改进“alynx-live-wallpaper”解决方案,并且我已经分叉了它(here),将所有内容转换为 Kotlin,然后尝试了解发生了什么,解决它所存在的问题,并进行现代化正在路上。
为了使其工作更加容易,我创建了另一个存储库(基于我的分支),这次尝试尽可能减少代码,播放应用程序内置的视频,只是为了集中精力的问题,然后看看我应该做什么。该存储库可以在此处找到。
我将重点关注 OpenGL3,尽管 OpenGL2 上似乎也存在同样的情况。
起初我认为这可能是一个与线程相关的问题(因为我可以看到一些函数在 UI 线程上调用,一些函数在后台线程上调用),但即使我认为这里在技术上存在与线程相关的问题,这不是我所看到的真正原因。然后我认为错误在
setOffset
,因为它使用maxXOffset
作为y偏移的新值,而不是maxYOffset
(报告这里),但这仍然不是原因。
对于带有位图的画布,我过去创建的并且即使水平滚动也可以正常工作,是这样的:
val canvasWidth = canvas.width
val canvasHeight = canvas.height
val bitmapWidth = bitmap.width.toFloat()
val bitmapHeight = bitmap.height.toFloat()
val scale = max(canvasWidth / bitmapWidth, canvasHeight / bitmapHeight)
val x = currentxOffset!! * (canvasWidth - bitmapWidth * scale)
val y = (canvasHeight - bitmapHeight * scale) / 2
canvas.save()
canvas.translate(x, y)
canvas.drawBitmap(bitmap, 0f, 0f, null)
canvas.restore()
在我为视频创建的项目中,它似乎是这样工作的:
GLWallpaperService.kt 它将回调传递给 OpenGL 渲染器类:
@UiThread
override fun onOffsetsChanged(xOffset: Float, yOffset: Float, xOffsetStep: Float, yOffsetStep: Float, xPixelOffset: Int, yPixelOffset: Int) {
super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset)
if (allowSlide && !isPreview) {
renderer!!.setOffset(0.5f - xOffset, 0.5f - yOffset)
}
}
@UiThread
override fun onSurfaceChanged(surfaceHolder: SurfaceHolder, format: Int, width: Int, height: Int) {
super.onSurfaceChanged(surfaceHolder, format, width, height)
renderer!!.setScreenSize(width, height)
}
此外,它还查找视频属性:
videoRotation = rotation!!.toInt()
videoWidth = width!!.toInt()
videoHeight = height!!.toInt()
GLES30WallpaperRenderer.kt 这负责实际的计算和绘图。我会尽量减少这里的代码:
@UiThread
override fun setScreenSize(width: Int, height: Int) {
if (screenWidth != width || screenHeight != height) {
screenWidth = width
screenHeight = height
maxXOffset =
(1.0f - screenWidth.toFloat() / screenHeight / (videoWidth.toFloat() / videoHeight)) / 2
maxYOffset =
(1.0f - screenHeight.toFloat() / screenWidth / (videoHeight.toFloat() / videoWidth)) / 2
updateMatrix()
}
}
override fun setOffset(xOffset: Float, yOffset: Float) {
val newXOffset = xOffset.coerceAtLeast(-maxXOffset).coerceAtMost(maxXOffset)
val newYOffset = yOffset.coerceAtLeast(-maxYOffset).coerceAtMost(maxYOffset)
if (this.xOffset != newXOffset || this.yOffset != newYOffset) {
this.xOffset = newXOffset
this.yOffset = newYOffset
updateMatrix()
}
}
@UiThread
private fun updateMatrix() {
for (i in 0..15) {
mvp[i] = 0.0f
}
mvp[15] = 1.0f
mvp[10] = mvp[15]
mvp[5] = mvp[10]
mvp[0] = mvp[5]
val videoRatio = videoWidth.toFloat() / videoHeight
val screenRatio = screenWidth.toFloat() / screenHeight
if (videoRatio >= screenRatio) {
Matrix.scaleM(mvp, 0, videoWidth.toFloat() / videoHeight / (screenWidth.toFloat() / screenHeight), 1f, 1f)
if (videoRotation % 360 != 0) {
Matrix.rotateM(mvp, 0, -videoRotation.toFloat(), 0f, 0f, 1f)
}
Matrix.translateM(mvp, 0, xOffset, 0f, 0f)
} else {
Matrix.scaleM(mvp, 0, 1f, videoHeight.toFloat() / videoWidth / (screenHeight.toFloat() / screenWidth), 1f)
if (videoRotation % 360 != 0) {
Matrix.rotateM(mvp, 0, -videoRotation.toFloat(), 0f, 0f, 1f)
}
Matrix.translateM(mvp, 0, 0f, yOffset, 0f)
}
}
视频滚动有什么问题吗?
这是一个额外的举动:我怎样才能添加背景颜色和/或图像(随着滚动而移动)?我记得我在 OpenGL 1.x 上做了类似的事情,但从那时起事情可能发生了变化......
尝试制作 GLES30WallpaperRenderer.kt
class GLES30WallpaperRenderer : GLSurfaceView.Renderer {
// ... Ur code ...
private var bGroundTextureId: Int = -1
private var bGroundShaderProgram: Int = -1
// New variables to control the movement
private var offsetX: Float = 0.0f
private var offsetY: Float = 0.0f
private fun setupBGround() {
// Load b-ground image texture if you have one
bGroundTextureId = TextureUtils.loadTexture(context, R.drawable.background_image)
// Create and compile b-ground shader program
val vertexShaderCode = getVertexShaderCodeWithOffset() // Vertex shader code with offset
val fragmentShaderCode = ... // Fragment shader code for b-ground quad
bGroundShaderProgram = ShaderUtils.createProgram(vertexShaderCode, fragmentShaderCode)
}
// Add a uniform variable to the vertex shader to control the offset
private fun getVertexShaderCodeWithOffset(): String {
return """
// ... Your other vertex shader code ...
// Uniform variable for offset
uniform vec2 u_Offset;
void main() {
// ... Your other vertex shader code ...
// Apply the offset to the vertex position
gl_Position = vec4(a_Position.xy + u_Offset, 0.0, 1.0);
}
""".trimIndent()
}
// Function to update the offset values for movement
fun updateOffset(offsetX: Float, offsetY: Float) {
this.offsetX = offsetX
this.offsetY = offsetY
}
private fun drawBGround() {
// Bind the b-ground shader program
GLES30.glUseProgram(bGroundShaderProgram)
// Set the offset uniform in the shader
val offsetLocation = GLES30.glGetUniformLocation(bGroundShaderProgram, "u_Offset")
GLES30.glUniform2f(offsetLocation, offsetX, offsetY)
// Bind the texture and draw the b-ground quad
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, bGroundTextureId)
// Set texture coordinates, vertex coordinates, and other attributes for the b-ground quad
// Draw the b-ground quad using GLES30.glDrawArrays() or GLES30.glDrawElements()
}
// ... Ur other code ...
}