mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add confetti overlay to Effect demo
Added the first overlay effect to the Effect demo. It includes an emitter of confetti that drops from the center of the frame. PiperOrigin-RevId: 712940163
This commit is contained in:
parent
229aadc91b
commit
6f187a3859
20
demos/effect/lint.xml
Normal file
20
demos/effect/lint.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2024 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<lint>
|
||||
<issue id="UnsafeOptInUsageError">
|
||||
<option name="opt-in" value="androidx.media3.common.util.UnstableApi" />
|
||||
</issue>
|
||||
</lint>
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.media3.demo.effect
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Handler
|
||||
import androidx.media3.common.VideoFrameProcessingException
|
||||
import androidx.media3.common.util.Size
|
||||
import androidx.media3.common.util.Util
|
||||
import androidx.media3.effect.CanvasOverlay
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
/** Mimics an emitter of confetti, dropping from the center of the frame. */
|
||||
internal class ConfettiOverlay : CanvasOverlay(/* useInputFrameSize= */ true) {
|
||||
|
||||
private val confettiList = mutableListOf<Confetti>()
|
||||
private val paint = Paint()
|
||||
private val handler = Handler(Util.getCurrentOrMainLooper())
|
||||
|
||||
private var addConfettiTask: (() -> Unit)? = null
|
||||
private var width = 0f
|
||||
private var height = 0f
|
||||
private var started = false
|
||||
|
||||
override fun configure(videoSize: Size) {
|
||||
super.configure(videoSize)
|
||||
this.width = videoSize.width.toFloat()
|
||||
this.height = videoSize.height.toFloat()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onDraw(canvas: Canvas, presentationTimeUs: Long) {
|
||||
if (!started) {
|
||||
start()
|
||||
}
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
|
||||
confettiList.removeAll { confetti ->
|
||||
confetti.y > height / 2 || confetti.x <= 0 || confetti.x > width
|
||||
}
|
||||
for (confetti in confettiList) {
|
||||
confetti.draw(canvas, paint)
|
||||
confetti.update()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(VideoFrameProcessingException::class)
|
||||
override fun release() {
|
||||
super.release()
|
||||
handler.post(this::stop)
|
||||
}
|
||||
|
||||
/** Starts the confetti. */
|
||||
fun start() {
|
||||
addConfettiTask = this::addConfetti
|
||||
handler.post(checkNotNull(addConfettiTask))
|
||||
started = true
|
||||
}
|
||||
|
||||
/** Stops the confetti. */
|
||||
fun stop() {
|
||||
handler.removeCallbacks(checkNotNull(addConfettiTask))
|
||||
confettiList.clear()
|
||||
started = false
|
||||
addConfettiTask = null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addConfetti() {
|
||||
repeat(5) {
|
||||
confettiList.add(
|
||||
Confetti(
|
||||
text = CONFETTI_TEXTS[abs(Random.nextInt()) % CONFETTI_TEXTS.size],
|
||||
x = width / 2f,
|
||||
y = EMITTER_POSITION_Y.toFloat(),
|
||||
size = CONFETTI_BASE_SIZE + Random.nextInt(CONFETTI_SIZE_VARIATION),
|
||||
color = Color.HSVToColor(floatArrayOf(Random.nextInt(360).toFloat(), 0.6f, 0.8f)),
|
||||
)
|
||||
)
|
||||
}
|
||||
handler.postDelayed(this::addConfetti, /* delayMillis= */ 100)
|
||||
}
|
||||
|
||||
private class Confetti(
|
||||
private val text: String,
|
||||
private val size: Int,
|
||||
private val color: Int,
|
||||
var x: Float,
|
||||
var y: Float,
|
||||
) {
|
||||
private val speedX = 4 * (Random.nextFloat() * 2 - 1) // Random speed in x direction
|
||||
private val speedY = 4 * Random.nextFloat() // Random speed in y direction
|
||||
private val rotationSpeed = (Random.nextFloat() - 0.5f) * 4f // Random rotation speed
|
||||
|
||||
private var rotation = Random.nextFloat() * 360f
|
||||
|
||||
/** Draws the [Confetti] on the [Canvas]. */
|
||||
fun draw(canvas: Canvas, paint: Paint) {
|
||||
canvas.save()
|
||||
paint.color = color
|
||||
canvas.translate(x, y)
|
||||
canvas.rotate(rotation)
|
||||
paint.textSize = size.toFloat()
|
||||
canvas.drawText(text, /* x= */ 0f, /* y= */ 0f, paint) // Only draw text
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
/** Updates the [Confetti]. */
|
||||
fun update() {
|
||||
x += speedX
|
||||
y += speedY
|
||||
rotation += rotationSpeed
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val CONFETTI_TEXTS = listOf("❊", "✿", "❊", "✦︎", "♥︎", "☕︎")
|
||||
const val EMITTER_POSITION_Y = -50
|
||||
const val CONFETTI_BASE_SIZE = 30
|
||||
const val CONFETTI_SIZE_VARIATION = 10
|
||||
}
|
||||
}
|
@ -68,8 +68,11 @@ import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util.SDK_INT
|
||||
import androidx.media3.effect.Contrast
|
||||
import androidx.media3.effect.OverlayEffect
|
||||
import androidx.media3.effect.TextureOverlay
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.google.common.collect.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class EffectActivity : ComponentActivity() {
|
||||
@ -279,7 +282,15 @@ class EffectActivity : ComponentActivity() {
|
||||
onClick = {
|
||||
val effectsList = mutableListOf<Effect>()
|
||||
|
||||
if (effectControlsState.contrastValue != 0f) {
|
||||
effectsList += Contrast(effectControlsState.contrastValue)
|
||||
}
|
||||
|
||||
val overlaysBuilder = ImmutableList.builder<TextureOverlay>()
|
||||
if (effectControlsState.confettiOverlayChecked) {
|
||||
overlaysBuilder.add(ConfettiOverlay())
|
||||
}
|
||||
effectsList += OverlayEffect(overlaysBuilder.build())
|
||||
|
||||
onApplyEffectsClicked(effectsList)
|
||||
effectControlsState = effectControlsState.copy(effectsChanged = false)
|
||||
@ -333,6 +344,17 @@ class EffectActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
EffectItem(
|
||||
name = stringResource(R.string.confetti_overlay),
|
||||
enabled = enabled,
|
||||
onCheckedChange = { checked ->
|
||||
onEffectControlsStateChange(
|
||||
effectControlsState.copy(effectsChanged = true, confettiOverlayChecked = checked)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,6 +404,7 @@ class EffectActivity : ComponentActivity() {
|
||||
data class EffectControlsState(
|
||||
val effectsChanged: Boolean = false,
|
||||
val contrastValue: Float = 0f,
|
||||
val confettiOverlayChecked: Boolean = false,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
@ -24,4 +24,5 @@
|
||||
<string name="can_not_open_file_error">"File couldn't be opened. Please try again."</string>
|
||||
<string name="permission_not_granted_error">"Permission was not granted."</string>
|
||||
<string name="contrast">Contrast</string>
|
||||
<string name="confetti_overlay">Confetti Overlay</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user