HELP! Image Target Lost with video Issue — Visual Studio ECS Code

Hello, I am trying to pause/stop a video playing on a plane within an image target, once that image target is lost. The below code does not throw up errors, however it does not work and I never get the console.log to show ‘Image lost — resetting videoPlane material.’ PLEASE HELP!

 import * as ecs from '@8thwall/ecs'

ecs.registerComponent({
  name: 'PauseVideoOnExit',
  schema: {
    imageTarget: ecs.eid,
    videoPlane: ecs.eid,
  },

  stateMachine: ({world, eid, schemaAttribute}) => {
    let videoElement: HTMLVideoElement | null = null

    return ecs.defineState('default')
      .initial()
      .onEnter(() => {
        const {imageTarget, videoPlane} = schemaAttribute.get(eid)

        if (!imageTarget || !videoPlane) {
          console.warn('PauseVideoOnExit: Missing imageTarget or videoPlane.')
          return
        }

        // Delay to allow material and texture to initialize
        setTimeout(() => {
          const videoPlaneObject = world.three.entityToObject.get(videoPlane)

          if (!videoPlaneObject) {
            console.warn('PauseVideoOnExit: No three.js object for videoPlane entity.')
            return
          }

          const {material} = videoPlaneObject as any

          if (!material) {
            console.warn('PauseVideoOnExit: No material found on videoPlane object.')
            return
          }

          const materials = Array.isArray(material) ? material : [material]
          let foundVideoElement: HTMLVideoElement | null = null

          for (const mat of materials) {
            const texture = mat.map
            const image = texture?.image

            if (image instanceof HTMLVideoElement) {
              foundVideoElement = image
              break
            } else {
              console.log('PauseVideoOnExit: material.map.image is NOT a video element. Found:', image?.constructor?.name)
            }
          }

          if (!foundVideoElement) {
            console.warn('PauseVideoOnExit: No HTMLVideoElement found in any material map image.')
            return
          }

          videoElement = foundVideoElement

          world.events.addListener(imageTarget, 'xrimagelost', () => {
            console.log('Image target lost — pausing and resetting video.')

            if (videoElement && !videoElement.paused) {
              videoElement.pause()
              videoElement.currentTime = 0
            }
          })
        }, 500)  // Wait 0.5s to ensure texture has loaded
      })
  },
})

Hi! I’ve added a ‘Video’ space to our Image Target sample project. This should help get you started.

George Butler you’re a hero! Been beating my head against the wall trying to figure this out. Seriously this made my day.

Not to overstay my welcome, but I have used the UI Video Controls ECS code (template) in my project. So now if I pause the video then go off target, then return to the target, the video starts automatically with the play button back over the video in a weird way. Any way to combine these? Again, I don’t want to stretch my ask.

Here is the UI code (from 8th Wall):

import * as ecs from '@8thwall/ecs'

ecs.registerComponent({
  name: 'Video Controls UI',
  schema: {
    // @required
    background: ecs.eid,
    // @required
    playbackImage: ecs.eid,
  },
  schemaDefaults: {
  },
  data: {
  },
  stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
    const toPause = ecs.defineTrigger()
    const toPlaying = ecs.defineTrigger()

    ecs.defineState('setup')
      .initial()
      .listen(eid, ecs.events.VIDEO_CAN_PLAY_THROUGH, (e) => {
        toPause.trigger()
      })
      .onTrigger(toPause, 'paused')

    ecs.defineState('paused')
      .onEnter(() => {
        const {background, playbackImage} = schemaAttribute.get(eid)

        ecs.Ui.mutate(world, background, (cursor) => {
          cursor.backgroundOpacity = 0.5
        })

        ecs.Ui.mutate(world, playbackImage, (cursor) => {
          cursor.image = 'assets/icons/play-button.png'
          cursor.opacity = 1
        })

        ecs.VideoControls.set(world, eid, {
          paused: true,
        })
      })
      .listen(eid, ecs.input.SCREEN_TOUCH_START, (e) => {
        toPlaying.trigger()
      })

      .onTrigger(toPlaying, 'playing')

    ecs.defineState('playing')
      .onEnter(() => {
        const {playbackImage} = schemaAttribute.get(eid)

        ecs.Ui.mutate(world, playbackImage, (cursor) => {
          cursor.image = 'assets/icons/pause-button.png'
        })

        ecs.VideoControls.set(world, eid, {
          paused: false,
        })
      })
      .onTick(() => {
        const {background, playbackImage} = schemaAttribute.get(eid)

        ecs.Ui.mutate(world, background, (cursor) => {
          cursor.backgroundOpacity -= 0.1
        })

        ecs.Ui.mutate(world, playbackImage, (cursor) => {
          cursor.opacity -= 0.1
        })
      })
      .listen(eid, ecs.input.SCREEN_TOUCH_START, (e) => {
        toPause.trigger()
      })
      .onTrigger(toPause, 'paused')
  },
})

No problem! :smiley: That’s what I’m here for.

I would approach this by combining the two components, which you might find easier working from the Video sample project since it already has the UI elements built over the video. You just need to add the event listener to the playing state so that on lost it transitions to the “paused” state and on the paused state attach an event listener that goes to the “playing” state when the target is found.

If you get stuck I can look into updating the Video sample project with an Image Target example.

I am a bit stuck, is it possible to update that Video sample project? Thanks again George!

I’ve added a Image Targets space to the Video sample project: Studio: Video | 8th Wall | 8th Wall

Let me know if that helps!

Incredible George! Thank you!…

Quick question (maybe should start a new topic). In the 8th Wall project library I have been playing around with your “Spatial Audio” example. I cloned the project and also replicated the .ts code in my working project. In both cases, the audio requires me to click the button (boombox) twice to make it start.

It works great, I am just unsure if the need for two clicks is my device or this could be resolved? Any guidance would be deeply appreciated.

I’ll take a look at the project, I might have misconfigured the state machine or something similar during an update.