Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add exoplayer node sample #520

Merged
merged 4 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions samples/ar-augmented-image/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ android {
dependencies {
implementation project(":samples:common")

// ExoPlayer
implementation("androidx.media3:media3-exoplayer:1.3.1")
implementation("androidx.media3:media3-ui:1.3.1")

// ARSceneview
releaseImplementation "io.github.sceneview:arsceneview:2.2.0"
debugImplementation project(":arsceneview")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@ import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import io.github.sceneview.ar.ARSceneView
import io.github.sceneview.ar.arcore.addAugmentedImage
import io.github.sceneview.ar.arcore.getUpdatedAugmentedImages
import io.github.sceneview.ar.node.AugmentedImageNode
import io.github.sceneview.math.Position
import io.github.sceneview.node.ModelNode
import io.github.sceneview.sample.araugmentedimage.video.ExoPlayerNode

class MainFragment : Fragment(R.layout.fragment_main) {

lateinit var sceneView: ARSceneView

val augmentedImageNodes = mutableListOf<AugmentedImageNode>()

// TODO: Restore when
// var qrCodeNode: VideoNode? = null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand Down Expand Up @@ -51,40 +52,23 @@ class MainFragment : Fragment(R.layout.fragment_main) {
)
)

"qrcode" -> {}
// TODO: Wait for VideoNode to come back
// addChildNode(VideoNode(
// materialLoader = materialLoader,
// player = MediaPlayer().apply {
// setDataSource(
// requireContext(),
// Uri.parse("https://sceneview.github.io/assets/videos/ads/ar_camera_app_ad.mp4")
// )
// isLooping = true
// setOnPreparedListener {
// if (augmentedImage.isTracking) {
// start()
// }
// }
// prepareAsync()
// }
// ).also { qrCodeNode ->
// onTrackingStateChanged = { trackingState ->
// when (trackingState) {
// TrackingState.TRACKING -> {
// if (!qrCodeNode.player.isPlaying) {
// qrCodeNode.player.start()
// }
// }
//
// else -> {
// if (qrCodeNode.player.isPlaying) {
// qrCodeNode.player.pause()
// }
// }
// }
// }
// })
"qrcode" -> {
addChildNode(
ExoPlayerNode(
engine = engine,
materialLoader = materialLoader,
// size = Size(x = augmentedImage.extentX, y = augmentedImage.extentZ), // When the width of the image is set
exoPlayer = ExoPlayer.Builder(requireContext()).build()
.apply {
setMediaItem(MediaItem.fromUri("https://sceneview.github.io/assets/videos/ads/ar_camera_app_ad.mp4"))
prepare()
playWhenReady = true
repeatMode = Player.REPEAT_MODE_ALL
},
// chromaKeyColor = if (chromaKey) 0x2fff19 else null, // 0x2fff19 is colorOf(0.1843f, 1.0f, 0.098f)
)
)
}
}
}
addChildNode(augmentedImageNode)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package io.github.sceneview.sample.araugmentedimage.video

import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import com.google.android.filament.Engine
import com.google.android.filament.RenderableManager
import dev.romainguy.kotlin.math.normalize
import io.github.sceneview.geometries.Plane
import io.github.sceneview.loaders.MaterialLoader
import io.github.sceneview.material.setExternalTexture
import io.github.sceneview.math.Direction
import io.github.sceneview.math.Position
import io.github.sceneview.math.Size
import io.github.sceneview.node.PlaneNode

open class ExoPlayerNode(
private val videoMaterial: VideoMaterial,
val exoPlayer: ExoPlayer,
size: Size = Plane.DEFAULT_SIZE,
center: Position = Plane.DEFAULT_CENTER,
normal: Direction = Plane.DEFAULT_NORMAL,
rotateToNode: Boolean = false,
builderApply: RenderableManager.Builder.() -> Unit = {},
) : PlaneNode(
engine = videoMaterial.engine,
size = if (rotateToNode) Size(x = size.x, y = 0.0f, z = size.y) else size,
center = if (rotateToNode) Position(x = 0.0f, y = center.z, z = 0.0f) else center,
normal = normal,
materialInstance = videoMaterial.instance,
builderApply = builderApply,
) {
init {
exoPlayer.setVideoSurface(videoMaterial.surface)
if (size == Plane.DEFAULT_SIZE) {
exoPlayer.doOnVideoSized { player, width, height ->
if (exoPlayer == player) {
updateGeometry(
size = normalize(
when (rotateToNode) {
true -> Size(x = width.toFloat(), y = 0.0f, z = height.toFloat())
false -> Size(x = width.toFloat(), y = height.toFloat(), z = 0.0f)
},
),
)
}
}
}
}

constructor(
engine: Engine,
materialLoader: MaterialLoader,
exoPlayer: ExoPlayer,
chromaKeyColor: Int? = null,
rotateToNode: Boolean = false,
size: Size = Plane.DEFAULT_SIZE,
center: Position = Plane.DEFAULT_CENTER,
normal: Direction = Plane.DEFAULT_NORMAL,
builderApply: RenderableManager.Builder.() -> Unit = {},
) : this(
videoMaterial = VideoMaterial(
engine = engine,
materialLoader = materialLoader,
chromaKeyColor = chromaKeyColor,
),
exoPlayer = exoPlayer,
rotateToNode = rotateToNode,
size = size,
center = center,
normal = normal,
builderApply = builderApply,
)

override fun destroy() {
super.destroy()
exoPlayer.release()
videoMaterial.destroy()
}

override fun updateVisibility() {
super.updateVisibility()
when (isVisible) {
true -> if (exoPlayer.isPlaying.not()) exoPlayer.play()
else -> if (exoPlayer.isPlaying) exoPlayer.pause()
}
}
}

@OptIn(UnstableApi::class)
private fun ExoPlayer.doOnVideoSized(block: (player: ExoPlayer, videoWidth: Int, videoHeight: Int) -> Unit) {
val videoWidth = videoFormat?.width ?: 0
val videoHeight = videoFormat?.height ?: 0
if (videoWidth > 0 && videoHeight > 0) {
block(this, videoWidth, videoHeight)
} else {
addListener(
object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == Player.STATE_READY) {
val width = videoFormat?.width ?: 0
val height = videoFormat?.height ?: 0
if (width > 0 && height > 0) {
block(this@doOnVideoSized, width, height)
}
}
}
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.github.sceneview.sample.araugmentedimage.video

import android.graphics.SurfaceTexture
import android.view.Surface
import com.google.android.filament.Engine
import com.google.android.filament.MaterialInstance
import com.google.android.filament.Stream
import io.github.sceneview.loaders.MaterialLoader
import io.github.sceneview.material.setExternalTexture
import io.github.sceneview.safeDestroyStream
import io.github.sceneview.safeDestroyTexture
import io.github.sceneview.texture.VideoTexture

class VideoMaterial internal constructor(
val engine: Engine,
materialLoader: MaterialLoader,
chromaKeyColor: Int? = null,
) {
/**
* Images drawn to the Surface will be made available to the Filament Stream.
*/
val surfaceTexture = SurfaceTexture(0).apply {
detachFromGLContext()
}

/**
* The Android surface.
*/
val surface = Surface(surfaceTexture)

/**
* The Filament Stream.
*/
val stream = Stream.Builder()
.stream(surfaceTexture)
.build(engine)

/**
* The Filament Texture diffusing the stream.
*/
val texture = VideoTexture.Builder()
.stream(stream)
.build(engine)

val instance: MaterialInstance = materialLoader.createVideoInstance(texture, chromaKeyColor)

init {
instance.setExternalTexture(texture)
}

fun destroy() {
engine.safeDestroyTexture(texture)
engine.safeDestroyStream(stream)
surface.release()
surfaceTexture.release()
}
}
Loading