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