# Adding Video Recording/ Sharing to Niantic Studio Project

**URL:** <https://forum.8thwall.com/t/adding-video-recording-sharing-to-niantic-studio-project/6405>\
**Category:** Technical Support\
**Created:** [May 8, 2025, 8:07pm UTC](https://forum.8thwall.com/t/adding-video-recording-sharing-to-niantic-studio-project/6405 "2025-05-08T20:07:30Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Lerma\_DX](https://avatars.discourse-cdn.com/v4/letter/l/e5b9ba/32.png) [@Lerma\_DX](https://forum.8thwall.com/u/Lerma_DX)\
**Post date:** [May 8, 2025, 8:07pm UTC](https://forum.8thwall.com/t/adding-video-recording-sharing-to-niantic-studio-project/6405/1 "2025-05-08T20:07:30Z")

</div>

If I wanted to Add and Customize Video Recording/ Sharing to Niantic Studio Project do I need to clone the entire XRExtras repository inside my project or can I only use a specific portion of it?

The XRExtras [MediaRecorder Repo](https://github.com/8thwall/web/tree/master/xrextras/src/mediarecorder)

---

<div class="post-metadata">

**Author:** ![GeorgeButler](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.8thwall.com/georgebutler/32/1903_2.png) [@GeorgeButler](https://forum.8thwall.com/u/GeorgeButler)\
**Post date:** [May 9, 2025, 6:35pm UTC](https://forum.8thwall.com/t/adding-video-recording-sharing-to-niantic-studio-project/6405/2 "2025-05-09T18:35:21Z")

</div>

XRExtras is not compatible with Studio, you’ll have to build it out yourself. However we’re working on making these features apart of the sample project(s).

> [@How to take picture?](https://forum.8thwall.com/t/how-to-take-picture/6372/2):
>
> Hi, welcome to the forums! We’re currently working on this as a sample project feature. In the meantime you can build it out yourself by following the instructions in this thread:

---

<div class="post-metadata">

**Author:** ![Lerma\_DX](https://avatars.discourse-cdn.com/v4/letter/l/e5b9ba/32.png) [@Lerma\_DX](https://forum.8thwall.com/u/Lerma_DX)\
**Post date:** [May 12, 2025, 6:31pm UTC](https://forum.8thwall.com/t/adding-video-recording-sharing-to-niantic-studio-project/6405/3 "2025-05-12T18:31:58Z")

</div>

When could I look forward to have that functionality in the sample projects? Just wondering if I should spend my time figuring it out now or I could wait a bit, Thanks!

---

<div class="post-metadata">

**Author:** ![GeorgeButler](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.8thwall.com/georgebutler/32/1903_2.png) [@GeorgeButler](https://forum.8thwall.com/u/GeorgeButler)\
**Post date:** [May 12, 2025, 6:39pm UTC](https://forum.8thwall.com/t/adding-video-recording-sharing-to-niantic-studio-project/6405/4 "2025-05-12T18:39:05Z")

</div>

I would say go for it! We’re still working on adding the functionality and there’s no definitive date I can give.

---

<div class="post-metadata">

**Author:** ![Lerma\_DX](https://avatars.discourse-cdn.com/v4/letter/l/e5b9ba/32.png) [@Lerma\_DX](https://forum.8thwall.com/u/Lerma_DX)\
**Post date:** [May 14, 2025, 2:23am UTC](https://forum.8thwall.com/t/adding-video-recording-sharing-to-niantic-studio-project/6405/5 "2025-05-14T02:23:29Z")

</div>

Ok so it’s not perfect but I’m capturing the screenshot and letting the user share it natively. Would be great to get some feedback cause I’m probably not doing this optimally

```ts
import * as ecs from '@8thwall/ecs' // This is how you access the ecs library.
import {shareImage} from './share'
declare const XR8: any

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,
  },
  // schemaDefaults: {
  // button: 'screenshotBtn',
  // previewImage: 'screenshotPreview',
  // },
  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,
    } = schemaAttribute.get(eid)
    const data = dataAttribute.cursor(eid)
    const toPreview = ecs.defineTrigger()
    const toIdle = ecs.defineTrigger()

    // ─── Idle ───────────────────────────────────────────────────────────
    ecs.defineState('idle')
      .initial()
      .onEnter(() => {
        console.log('Entered Default State')

        // Full-screen overlay layout
        ecs.Ui.set(world, overlayFrame, {
          type: 'overlay',
          display: 'flex',
          flexDirection: 'column',
          width: '100%',
          height: '100%',
          padding: '2%',
          backgroundOpacity: 0.0,
          opacity: 1.0,
          justifyContent: 'flex-end',
          alignItems: 'center',
          gap: '0',
          background: '#151414',
          backgroundSize: 'contain',
          borderRadius: 0,
        })

        // Top container holds only previewFrame
        ecs.Ui.set(world, topFrame, {
          type: 'overlay',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          width: '100%',
          height: '80%',
          backgroundOpacity: 0.0,
          background: '#1F6ED6',
          opacity: 1.0,
          justifyContent: 'flex-end',
          gap: '0',
          padding: '0',
          backgroundSize: 'contain',
          borderRadius: 0,
        })

        // Preview frame: row layout for close button + image
        ecs.Ui.set(world, previewFrame, {
          type: 'overlay',
          display: 'flex',
          position: 'relative',
          flexDirection: 'row',
          justifyContent: 'flex-end',
          alignItems: 'flex-start',
          borderColor: '#FFFFF',
          borderWidth: 2,
          borderRadius: 12,
          paddingBottom: '10',
          backgroundOpacity: 0.0,
          opacity: 1.0,
          gap: '0',
          background: '#141415',
          backgroundSize: 'contain',
          width: 'auto',
          height: 'auto',
          maxWidth: '90%',
          maxHeight: '90%',
        })

        // Close button sits at top-right inside previewFrame
        ecs.Ui.set(world, previewCloseButton, {
          type: 'overlay',
          position: 'absolute',
          top: '36',
          right: '36',
          width: '36',
          height: '36',
          borderRadius: 18,
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          background: '#AD50FF',
          backgroundOpacity: 1,
          opacity: 1.0,
          flexDirection: 'row',
          gap: '0',
          padding: '0',
        })

        // Preview image auto-sizes to its intrinsic dimensions,
        // with max 100% of frame and no shrink
        ecs.Ui.set(world, previewImage, {
          type: 'overlay',
          maxWidth: '100%',
          maxHeight: '100%',
          overflow: 'hidden',
          flexShrink: 0,
          flexGrow: 1,
          backgroundSize: 'contain',
          backgroundOpacity: 0.00,
          borderRadius: 0.00,
        })

        // Initial visibility
        ecs.Hidden.set(world, topFrame)
        ecs.Hidden.remove(world, captureButton)
        ecs.Hidden.set(world, shareButton)
      })
      .onEvent(ecs.input.UI_CLICK, 'screenshot', {target: captureButton})

    // ─── Screenshot ──────────────────────────────────────────────────────
    ecs.defineState('screenshot')
      .onEnter(() => {
        // hide button
        ecs.Hidden.set(world, captureButton)
        // Hook into XR8 screenshot pipeline
        XR8.addCameraPipelineModule(
          XR8.canvasScreenshot().cameraPipelineModule()
        )
        // Capture screenshot
        XR8.canvasScreenshot().takeScreenshot()
          .then((base64: string) => {
            data.imageData = base64
            // Notify that screenshot is ready
            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 cliked')
        try {
          // 1) Decode base64 to bytes
          const b64 = data.imageData
          const binary = atob(b64)
          const len = binary.length
          const bytes = new Uint8Array(len)
          for (let i = 0; i < len; i++) {
            bytes[i] = binary.charCodeAt(i)
          }

          // 2) Create a Blob (PNG)
          const blob = new Blob([bytes], {type: 'image/png'})

          // 3) Invoke native share
          await shareImage(blob, 'screenshot.png')

          // 4) Go back to idle (or close preview)
          toIdle.trigger()
        } catch (err) {
          console.error('Share failed:', err)
        }
      })
  },
})

```

```ts
// share.ts

// Make this file a module so our `declare global` takes effect:
export {}

// --- TYPE AUGMENTATION WORKAROUND ----------------------

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[];
  }
}

// --- UTILITY FUNCTION---------------------------------------

/**
 * Shares an image blob using the Web Share API Level 2.
 * @param blob The image Blob you want to share.
 * @param fileName Optional filename (defaults to "image.jpg").
 */
export async function shareImage(
  blob: Blob,
  fileName = 'image.jpg'
): 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: 'Check out this image',
      text: 'Sharing via Web Share API Level 2',
    })
    console.log('Share successful')
  } catch (err) {
    console.error('Share failed:', err)
  }
}

```
