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)
}
})
},
})
Thanks both for your help
I had a working prototype with using this code which Iβve not checked on in a few weeks. Come back today and itβs throwing errors across multiple things. @GeorgeButler has there been an update to the editor?
Not that Iβm aware of. Can you share the errors?
I emailed support about this - it seems like your UI is broken - canβt nest more that two elements, positioning not working etc.
So. This is driving me slightly mad. Iβm new to 8th wall. Is it actually impossible to add the functionality for the user to take a photo or video of what theyβre seeing on screen? p.s. Iβm new, so posting a bit of code doesnβt help me - I donβt even know where it goes? But then, surely if it was easy, it would exist?
Trying to follow this but Iβm baffledβ¦
Yeah this is a core component tbh and should be provided in the platform library to add to projects and allows visual customisation
Really could use this right about now