What are the options now for adding video/Image capture?
This is something weβre actively working on making into a sample project.
Do you have an ETA on that? Iβm in the middle of doing early dev work for a live project trying to get various functionality working and I could really do with a solution to this
Hereβs a very quick example I made.
import * as ecs from '@8thwall/ecs'
ecs.registerComponent({
name: 'Capture Screenshot',
schema: {
screenshotButton: ecs.eid,
previewImage: ecs.eid,
},
stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
const toLoaded = ecs.defineTrigger()
ecs.defineState('default')
.initial()
.onEnter(() => {
const {previewImage} = schemaAttribute.get(eid)
ecs.Hidden.set(world, previewImage)
})
.onTick(() => {
// @ts-ignore
if (window.XR8) {
world.time.setTimeout(() => {
toLoaded.trigger()
}, 500)
}
})
.onTrigger(toLoaded, 'loaded')
ecs.defineState('loaded')
.onEnter(() => {
const {XR8} = window as any
XR8.addCameraPipelineModule(XR8.CanvasScreenshot.pipelineModule())
})
.listen(schemaAttribute.get(eid).screenshotButton, ecs.input.UI_CLICK, (e) => {
const {XR8} = window as any
const {previewImage} = schemaAttribute.get(eid)
XR8.CanvasScreenshot
.takeScreenshot()
.then((data) => {
ecs.Ui.mutate(world, previewImage, (c) => {
c.image = `data:image/png;base64,${data}`
})
ecs.Hidden.remove(world, previewImage)
}, (error) => {
console.log(error)
// Handle screenshot error.
})
})
},
})
Hereβs a video of it in use and the component setup.
this is how I added native share functionality if anyone is interested:
import * as ecs from '@8thwall/ecs'
// βββ HELPERS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
declare global {
interface Navigator {
/** Web Share API L2: share File objects */
canShare?(data: ShareData): boolean;
}
interface ShareData {
/** Web Share API L2: an array of File objects to be shared */
files?: File[];
}
}
async function shareImage(
blob: Blob,
fileName = 'ReunionTower.jpg',
title = '',
text = ''
): Promise<void> {
const file = new File([blob], fileName, {type: blob.type})
if (!navigator.canShare?.({files: [file]})) {
console.warn('Native file sharing is not supported on this browser.')
return
}
try {
await navigator.share({
files: [file],
title,
text,
})
console.log('Share successful')
} catch (err) {
console.error('Share failed:', err)
}
}
// βββ COMPONENT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ecs.registerComponent({
name: 'screenshot-capture',
schema: {
overlayFrame: ecs.eid,
topFrame: ecs.eid,
previewFrame: ecs.eid,
previewImage: ecs.eid,
previewCloseButton: ecs.eid,
captureButton: ecs.eid,
shareButton: ecs.eid,
shareTitle: ecs.string,
shareText: ecs.string,
},
schemaDefaults: {
shareTitle: 'Default Title',
shareText: 'Look what I capture?! Custom share copy blabla..',
},
data: {
imageData: ecs.string, // Base64 image data
},
// add: (world, component) => {
// },
// tick: (world, component) => {
// },
// remove: (world, component) => {
// },
stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
const {
overlayFrame,
topFrame,
previewFrame,
captureButton,
previewImage,
previewCloseButton,
shareButton,
shareTitle,
shareText,
} = schemaAttribute.get(eid)
const data = dataAttribute.cursor(eid)
const toPreview = ecs.defineTrigger()
const toIdle = ecs.defineTrigger()
// One blob at a time, per-component instance:
let capturedBlob: Blob | null = null
// βββ Idle βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ecs.defineState('idle')
.initial()
.onEnter(() => {
// Initial visibility
ecs.Hidden.set(world, topFrame)
ecs.Hidden.remove(world, captureButton)
ecs.Hidden.set(world, shareButton)
// clear old blob
capturedBlob = null
})
.onEvent(ecs.input.UI_CLICK, 'screenshot', {target: captureButton})
// βββ Screenshot ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ecs.defineState('screenshot')
.onEnter(() => {
// hide button
ecs.Hidden.set(world, captureButton)
const {XR8} = window as any
// Hook into XR8 screenshot pipeline
XR8.addCameraPipelineModule(
XR8.canvasScreenshot().cameraPipelineModule()
)
// Capture screenshot
XR8.canvasScreenshot().takeScreenshot()
.then(async (base64: string) => {
data.imageData = base64
// convert immediately via fetch(dataURL)
const dataUrl = `data:image/png;base64,${base64}`
const resp = await fetch(dataUrl)
capturedBlob = await resp.blob()
toPreview.trigger()
})
.catch((err: any) => console.error('Screenshot error:', err))
})
// Move to preview once done
.onTrigger(toPreview, 'preview')
// βββ Preview βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ecs.defineState('preview')
.onEnter(() => {
ecs.Ui.mutate(world, previewImage, (cursor) => {
cursor.image = `data:image/png;base64,${data.imageData}`
})
world.time.setTimeout(() => {
// show imagepreview
ecs.Hidden.remove(world, topFrame)
// show closeButton
ecs.Hidden.remove(world, shareButton)
}, 40)
})
.onEvent(
ecs.input.UI_CLICK,
'idle',
{target: previewCloseButton}
)
.listen(shareButton, ecs.input.UI_CLICK, async () => {
console.log('share button clicked')
try {
if (!capturedBlob) {
console.warn('Still processing screenshotβ¦')
return
}
await shareImage(capturedBlob, 'screenshot.png', shareTitle, shareText)
} catch (err) {
console.error('Share failed:', err)
}
})
},
})
1 Like
Thanks both for your help