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.UnstableApi
|
||||||
import androidx.media3.common.util.Util.SDK_INT
|
import androidx.media3.common.util.Util.SDK_INT
|
||||||
import androidx.media3.effect.Contrast
|
import androidx.media3.effect.Contrast
|
||||||
|
import androidx.media3.effect.OverlayEffect
|
||||||
|
import androidx.media3.effect.TextureOverlay
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class EffectActivity : ComponentActivity() {
|
class EffectActivity : ComponentActivity() {
|
||||||
@ -279,7 +282,15 @@ class EffectActivity : ComponentActivity() {
|
|||||||
onClick = {
|
onClick = {
|
||||||
val effectsList = mutableListOf<Effect>()
|
val effectsList = mutableListOf<Effect>()
|
||||||
|
|
||||||
|
if (effectControlsState.contrastValue != 0f) {
|
||||||
effectsList += Contrast(effectControlsState.contrastValue)
|
effectsList += Contrast(effectControlsState.contrastValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
val overlaysBuilder = ImmutableList.builder<TextureOverlay>()
|
||||||
|
if (effectControlsState.confettiOverlayChecked) {
|
||||||
|
overlaysBuilder.add(ConfettiOverlay())
|
||||||
|
}
|
||||||
|
effectsList += OverlayEffect(overlaysBuilder.build())
|
||||||
|
|
||||||
onApplyEffectsClicked(effectsList)
|
onApplyEffectsClicked(effectsList)
|
||||||
effectControlsState = effectControlsState.copy(effectsChanged = false)
|
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(
|
data class EffectControlsState(
|
||||||
val effectsChanged: Boolean = false,
|
val effectsChanged: Boolean = false,
|
||||||
val contrastValue: Float = 0f,
|
val contrastValue: Float = 0f,
|
||||||
|
val confettiOverlayChecked: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -24,4 +24,5 @@
|
|||||||
<string name="can_not_open_file_error">"File couldn't be opened. Please try again."</string>
|
<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="permission_not_granted_error">"Permission was not granted."</string>
|
||||||
<string name="contrast">Contrast</string>
|
<string name="contrast">Contrast</string>
|
||||||
|
<string name="confetti_overlay">Confetti Overlay</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user