Proper URI Handling for Local Assets in Kepler (ToastKepler and W3C Media Video)

I am working on a Fire TV application using the Kepler/Vega framework. I am having difficulty resolving local asset paths for two specific Amazon-developed libraries.

Environment:

  • SDK: Kepler/Vega

  • Libraries: @amazon-devices/react-native-kepler and @amazon-devices/react-native-w3cmedia

The Issues:

  1. ToastKepler Icon: When using ToastKepler.show, providing a local asset via require('./assets/logo.png') does not display the icon. This component require a string URI, what is the expected protocol for bundled assets?

  2. W3C Media Video: When I set the src=”https://example-video.com” it works but when I tried local video.mp4 from assets its not working. I suspect the W3C engine is not resolving the React Native numeric asset ID.

What I tried:

const logo = require('./assets/logo.png');
const video = require('./assets/video.mp4');
ToastKepler.show({ iconUri: logo });
ToastKepler.show({ iconUri: Image.resolveAssetSource(logo).uri });
<Video src={Image.resolveAssetSource(video).uri} ... />

Question: What are the recommended URI schemes (e.g., asset:///, res:///, or file:///) for these two components to ensure the native hardware layers can access the files bundled in the VPKG?

Hello @yali

ToastKepler and W3C Media Video components require specific URI schemes for bundled assets in Vega/Kepler framework.

Use asset:// Protocol

For ToastKepler Icon

import { useToastKepler } from '@amazon-devices/react-native-kepler';
const showToast = () => {
  ToastKepler.show({
    message: 'Success!',
    iconUri: 'asset://images/logo.png',  // Path relative to assets folder
    duration: 3000
  });
};

For W3C Media Video

import { VideoPlayer, KeplerVideoView } from '@amazon-devices/react-native-w3cmedia';

const BundledVideoPlayer = () => {
  const video = useRef<VideoPlayer | null>(null);
  const [useKeplerVideoView, setUseKeplerVideoView] = useState(false);

  useEffect(() => {
    initializeVideo();
  }, []);

  const initializeVideo = async () => {
    video.current = new VideoPlayer();
    await video.current.initialize();
    
   video.current.src = 'asset://video/sample.mp4';  // Path relative to assets folder
    
    video.current.autoplay = false;
    setUseKeplerVideoView(true);
  };

  return (
    <View style={styles.container}>
      {useKeplerVideoView && video.current && (
        <KeplerVideoView
          showControls={true}
          videoPlayer={video.current}
        />
      )}
    </View>
  );
};

Working Examples

  • Example 1:

ToastKepler with Local Icon

import React from 'react';
import { View, Button, StyleSheet } from 'react-native';
import { useToastKepler } from '@amazon-devices/react-native-kepler';

export const ToastExample = () => {
  const showSuccessToast = () => {
    ToastKepler.show({
      message: 'Operation successful!',
      iconUri: 'asset://images/success-icon.png',
      duration: 3000,
      position: 'bottom'
    });
  };

  const showErrorToast = () => {
    ToastKepler.show({
      message: 'Something went wrong',
      iconUri: 'asset://images/error-icon.png',
      duration: 3000,
      position: 'bottom'
    });
  };

  return (
    <View style={styles.container}>
      <Button title="Show Success" onPress={showSuccessToast} />
      <Button title="Show Error" onPress={showErrorToast} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 20
  }
});
  • Example 2:

W3C Media Video with Local Asset

import React, { useRef, useEffect, useState } from 'react';
import { View, StyleSheet, Button } from 'react-native';
import { VideoPlayer, KeplerVideoView } from '@amazon-devices/react-native-w3cmedia';

export const LocalVideoPlayer = () => {
  const video = useRef<VideoPlayer | null>(null);
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    initializeVideo();
    
    return () => {
      if (video.current) {
        video.current.pause();
        video.current.currentTime = 0;
      }
    };
  }, []);

  const initializeVideo = async () => {
    video.current = new VideoPlayer();
    await video.current.initialize();
    
    // Use asset:// protocol for bundled video
    video.current.src = 'asset://video/intro.mp4';
    video.current.autoplay = false;
    
    setIsReady(true);
  };

  const handlePlay = () => {
    video.current?.play();
  };

  const handlePause = () => {
    video.current?.pause();
  };

  return (
    <View style={styles.container}>
      {isReady && video.current && (
        <>
          <KeplerVideoView
            showControls={true}
            videoPlayer={video.current}
            style={styles.video}
          />
          <View style={styles.controls}>
            <Button title="Play" onPress={handlePlay} />
            <Button title="Pause" onPress={handlePause} />
          </View>
        </>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000'
  },
  video: {
    flex: 1
  },
  controls: {
    flexDirection: 'row',
    justifyContent: 'center',
    gap: 20,
    padding: 20
  }
});

Warm Regards,
Ivy

1 Like

Hi @Ivy_Mahajan ,

Thanks for the response, your provided examples were extremely helpful. But could you please be more specific about the placement of assets folder in the project and elaborate if I need to make any changes in the babel.config.js or metro.config.js or manifest.toml regarding this.
I tried the solution you provide but still I cant access the files(.png & .mp4), neither from root/assets nor from root/src/assets

Hi @yali

Your assets aren’t being bundled into the VPKG, which is why asset:// fails from both locations.

Root Cause
Without proper configuration, Vega doesn’t include assets in the build - the files literally don’t exist in the package.

Solution to try
1. Assets Location
Place at project root (not src/assets/):
your-app/
├── assets/
│ ├── images/logo.png
│ └── video/sample.mp4
├── manifest.toml
└── metro.config.js

2. Add to manifest.toml

[[assets]]
source = "assets/"
destination = "assets/"

3. Update metro.config.js

const { getDefaultConfig } = require('@react-native/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push('mp4', 'mov', 'webm');
module.exports = config;

4. Rebuild

rm -rf build/
npm start -- --reset-cache
npx react-native build-vega

Usage

// Path relative to assets/ folder (don't include "assets" in URI)
ToastKepler.show({
iconUri: 'asset://images/logo.png',
message: 'Success!'
});
video.current.src = 'asset://video/sample.mp4';

Verify Assets Are Bundled

vpt show-contents build/*.vpkg | grep assets

Should show your files. If not, check manifest.toml configuration.

Key Points

  • [[assets]] in manifest.toml is required - without it, assets aren’t bundled
  • URI path is relative to assets/ folder
  • Always rebuild after configuration changes

Please try and let me know if this resolves your issue!

Warm regards,
Ivy

1 Like

Hi @Ivy_Mahajan,

Here’s the output of the command: vpt show-contents build/armv7-debug/*.vpkg | grep assets

bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_jump_back_10_144dp.png
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_disable_loop.png
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox/UI/LogBoxImages/chevron-right.png
bundle/assets
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_caption_greyed.png
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_play_144dp.png
assets/image/banner_logo.png
assets
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_jump_forward_10_144dp.png
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image
bundle/assets/app.json
assets/image/logo.png
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox/UI/LogBoxImages
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox/UI
bundle/assets/node_modules
assets/image
assets/videos

bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox/UI/LogBoxImages/chevron-left.png
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox/UI/LogBoxImages/alert-triangle.png
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox
assets/videos/video.mp4
assets/asset-index.bin
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox/UI/LogBoxImages/close.png
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries/LogBox/UI/LogBoxImages/loader.png
assets/kepler-sdk-version
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_volume_mute.png
assets/raw/keplerscript-app-config.json
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_caption_on.png
assets/raw
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_caption_off.png
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_pause_144dp.png
bundle/assets/node_modules/@amazon-devices/react-native-kepler
bundle/assets/node_modules/@amazon-devices/react-native-kepler/Libraries
bundle/assets/node_modules/@amazon-devices
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_enable_loop.png
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia
bundle/assets/node_modules/@amazon-devices/react-native-w3cmedia/image/ic_volume_unmute.png

**But still when i use the asset protocol, there’s no image and video on screen.

  1. Assets Location**
    Place at project root (not src/assets/):
    your-app/
    ├── assets/
    │ ├── image/logo.png
    │ └── videos/video.mp4
    ├── manifest.toml
    └── metro.config.js**

Manifest.toml has this:**
[[assets]]
source = “assets/”
destination = “assets/”

metro.config.js:
const {getDefaultConfig} = require('@react-native/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.assetExts.push('mp4', 'mov', 'webm');
module.exports = config;

Usage:
ToastKepler.show({
iconUri: 'asset://image/logo.png',
message: 'Success!'
});
video.current.src = 'asset://videos/video.mp4';

Note:
When i tried with the video and image from the github url it worked.

Thanks @yali for the details.

Kindly allow me some time to analyze and look into this.

Warm regadrs,
Ivy

1 Like

Hey @yali

Do you think it might be possible for you to share a minimum repro of this issue (code snippets and examples)?
This way we can help you better.

Warm regards,
Ivy

Hi @Ivy_Mahajan ,

Here’s the usage example:

import { StyleSheet, View } from 'react-native';
import { useRef, useCallback, useEffect, useState } from 'react';
import { useToastKepler, ToastKeplerDuration } from '@amazon-devices/react-native-kepler';
import { VideoPlayer, KeplerVideoView } from '@amazon-devices/react-native-w3cmedia';

export const App = () => {
  const videoPlayerRef = useRef<VideoPlayer | null>(null);
  const [isVideoReady, setIsVideoReady] = useState(false);
  const ToastKepler = useToastKepler();

  const initializeVideo = useCallback(async () => {
    if (videoPlayerRef.current) return
    try {
      const player = new VideoPlayer();
      await player.initialize();

      player.loop = true;
      player.muted = true;
      player.src = "asset://videos/video.mp4";

      videoPlayerRef.current = player;
      setIsVideoReady(true);

      player.play().catch(err => console.error("Playback failed", err));
    } catch (error) {
      console.error("Video Initialization Error:", error);
    }
  }, []);

  useEffect(() => {
    initializeVideo();

    return () => {
      if (videoPlayerRef.current) {
        videoPlayerRef.current.pause();
        videoPlayerRef.current.deinitialize();
        videoPlayerRef.current = null;
      }
    };
  }, [initializeVideo]);

  ToastKepler.show({
      title: 'Test',
      description: `Testing the toast kepler`,
      iconUri: 'asset://images/toast.png',
      iconSize: 'small',
      duration: ToastKeplerDuration.SHORT
    });

  return (
    <View style={styles.container}>
      {isVideoReady && videoPlayerRef.current && (
        <View style={styles.videoWrapper}>
          <KeplerVideoView
            showControls={false}
            videoPlayer={videoPlayerRef.current}
          />
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white'
  },
  videoWrapper: {
    width: '100%',
    height: '100%',
    position: 'absolute'
  }
});

This example works fine with the https:// urls but not working with the local files using asset:// protocol.

  • Manifest and metro.config.js are same as mentioned above.
  • Assets are placed ath the project root

Hi @Ivy_Mahajan sorry I now understand you wanted a repo showing the issue. Please see that here - GitHub - Noitidart/vega-local-video-asset - even the remote video isn’t running. But it gets to success state. Same thing with local, it gets to success state but doesn’t load. I took the video from here - HTML Video

Thanks a ton @yali , this will help us dive deeper into the issue.
We are working on it.

Warm regards,
Ivy

1 Like

Thank you very much to you and team!

I got remote video playback working, I was missing stuff in the toml file. However the local video still doesn’t work. Please see latest repo I pushed up the commits - Commits · Noitidart/vega-local-video-asset · GitHub

1 Like

Thanks for the update @yali .
We are checking why the local video still doesn’t work.

Warm regards,
Ivy

1 Like

Hi @Ivy_Mahajan ,

Hope you’re doing good. Just a follow up message to know if there is any progress on the issue. I’m waiting to hear from you. Thanks again to you and your team.

Warm regards,
Yasir

Hi @yali

Yes, I was just about to get back to you. This is what we suggest as a short time workaround for now.

Please add the assets in raw directory under assets/ . You shouldn’t be adding any asset directory under assets directory. The raw directory is meant for any other asset outside the officially supported ones (i.e. images, text).

You can still access using /pkg/assets/raw/ . The path /pkg will always exists and is considered similar to an API. So, we will not change it.

In future there will be a Asset Resolver API to retrieve the files, but for now you can use the full raw folder path and move the video to raw folder. (But it is subject to change when you can use assets APIs to locate the file and then rely on the path for it during the specific playback.)

Warm regards,
Ivy

Hi @Ivy_Mahajan,

Thanks to you and your team for writing back.
I’ll try the solution provided by you and will get back to you.

Warm regards,
Yasir

Hi @Ivy_Mahajan,

Thanks for the clarification regarding using the assets/raw directory.

Could you please share a small working example of how the file should be referenced during playback? Specifically, I want to confirm the correct URI format to use (for example asset://, file:///, or direct /pkg/assets/raw/...).

A simple example of loading a video from the assets/raw folder would be very helpful.

Thanks again for your help!

Best regards
Yasir

Hi @yali

The main change you will have to do is change the videoSource to below:
const videoSource = '/pkg/assets/raw/mov_bbb.mp4';
Also put the actual video in the assets folder of the root directory of the project inside raw folder. ./assets/raw/mov_bb.mp4

I’ll come back with a full working code soon.

Warm regards,
Ivy