How can I remove or customize the default focus highlight (blur/dim overlay) on focused components in Kepler (Vega OS) TV apps?

When navigating through UI elements (e.g., using the TV remote) in my Kepler (Vega OS) TV app, every focused component gets a default dim/blur overlay applied by the system.

How can I disable or override this default focus highlight globally, or apply my own custom focus style instead?

Hello @Hoang_Dang ,

Thank you for your question about customizing the focus highlight in Kepler TV apps.
Our team will look into this and get back to you.

Regards,
Aishwarya

Hi @Hoang_Dang,

Welcome to the community!

Could you please specify the UI elements exhibiting this behaviour? If you have a code snippet which could demonstrate this behaviour, please share.

I think, you may be referring TouchableOpacity. Element TouchableOpacity has a prop activeOpacity which defaults to 0.2. When this element gets focussed, you will observe that it gets dimmed/blurred as per the default activeOpacity value. Depending on your requirement, you may choose to change activeOpacity or implement custom focus handling.

Below is a demo which compares the default activeOpacity with custom focus handling:

/* eslint-disable prettier/prettier */
import React, {useState} from 'react';
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';

const App = () => {
  const [feedbackHistory, setFeedbackHistory] = useState(['Ready']);
  const [button2Color, setButton2Color] = useState('#333333');

  const addFeedback = (message: string) => {
    setFeedbackHistory(prev => [...prev, message]);
  };

  return (
    <View style={styles.container}>
      <View style={styles.leftPane}>
        <TouchableOpacity
          style={styles.defaultStyleButton}
          onPress={() => addFeedback('Button 1 pressed (default Opacity is 1)')}
          onFocus={() => addFeedback('Button 1 focused (activeOpacity is by default 0.2)')}
          onBlur={() => addFeedback('Button 1 lost focus (default Opacity is 1)')}
        >
          <Text style={styles.buttonText}>Button 1: TouchableOpacity (default activeOpacity 0.2)</Text>
        </TouchableOpacity>
        <TouchableOpacity
          style={[styles.updatedStyleButton, {backgroundColor: button2Color}]}
          activeOpacity={1}
          onPress={() => {
            addFeedback('Button 2 pressed (changing backgroundColor to green)');
            setButton2Color('#116211ff');
          }}
          onFocus={() => {
            addFeedback('Button 2 focused (changing backgroundColor to blue)');
            setButton2Color('#0000ff');
          }}
          onBlur={() => {
            addFeedback('Button 2 lost focus (restored backgroundColor to grey)');
            setButton2Color('#333333');
          }}
        >
          <Text style={styles.buttonText}>Button 2: TouchableOpacity (activeOpacity is 1)</Text>
        </TouchableOpacity>
      </View>
      <View style={styles.rightPane}>
        <Text style={styles.feedbackText}>
          {feedbackHistory.join('\n')}
        </Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    padding: 20,
  },
  leftPane: {
    flex: 1,
    justifyContent: 'space-evenly',
    alignItems: 'center',
    paddingRight: 10,
  },
  rightPane: {
    flex: 1,
    backgroundColor: '#333333',
    padding: 15,
    borderRadius: 10,
  },
  defaultStyleButton: {
    alignItems: 'center',
    backgroundColor: '#333333',
    padding: 10,
  },
  updatedStyleButton: {
    alignItems: 'center',
    backgroundColor: '#333333',
    padding: 10,
  },

  buttonText: {
    color: 'white',
    fontSize: 18,
  },
  feedbackText: {
    color: 'white',
    fontSize: 16,
    lineHeight: 24,
  },
});

export default App;

Regards,
Siva

1 Like

More information - Focus Management | Design and Develop Vega Apps

1 Like

Hi Siva_Prakash,
After reviewing my application and comparing it with the code you shared, I realized that the issue was with the TouchableOpacity components — adding activeOpacity={1} helped me remove the default focus overlay that appeared on each item when focused.

Thank you so much for your support!

I’m currently working on the video playback feature. Could you recommend a library that can play videos? If possible, could you also share a simple example of how to use it? My video data will be fetched from Pixabay.

Once again, I’m really glad to have your help, and I hope to continue learning from you.

HI @Hoang_Dang,

Glad to know that your issue is resolved.

For video playback, you may refer ‘Vega Video App’ from Sample Apps | Vega Get Started

Regards,
Siva

Hi Siva_Prakash
I’ve read and analyzed it, and it seems a bit too complicated — is there any simpler example that can just play a basic video?
Regards,
Hoang Dang

Hi @Hoang_Dang,

Please check the Media Player documentation from here. Following this initial setup and this sample code could get you started with a simple media player app.

Regards,
Siva

Hi Siva Prakash,
I’ve tried setting it up and running it, but the result still isn’t working — every time I open the video, it only shows a black screen and doesn’t play. Could you please help me check what might be wrong? Thank you so much.
Here is my code

//PlayerScreen.tsx
import React, {useRef, useEffect, useState} from 'react';
import {StyleSheet, View, Text, TouchableOpacity, Platform} from 'react-native';
import {
  VideoPlayer,
  KeplerVideoSurfaceView,
  KeplerCaptionsView,
} from '@amazon-devices/react-native-w3cmedia';
import {
  ShakaPlayer,
  ShakaPlayerSettings,
} from '../../w3cmedia/shakaplayer/ShakaPlayer';

// ⚙️ Video sample (DASH)
const content = {
  secure: false,
  uri: 'https://cdn.pixabay.com/video/2019/04/06/22634-328940142_large.mp4',
  drm_scheme: '',
  drm_license_uri: '',
};

const DEFAULT_ABR_WIDTH = Platform.isTV ? 3840 : 1919;
const DEFAULT_ABR_HEIGHT = Platform.isTV ? 2160 : 1079;

export default function PlayerScreen() {
  const currShakaPlayerSettings = useRef<ShakaPlayerSettings>({
    secure: false,
    abrEnabled: true,
    abrMaxWidth: DEFAULT_ABR_WIDTH,
    abrMaxHeight: DEFAULT_ABR_HEIGHT,
  });

  const videoPlayer = useRef<VideoPlayer | null>(null);
  const shakaPlayer = useRef<any>(null);
  const [isReady, setIsReady] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [playerSettings] = useState<ShakaPlayerSettings>(
    currShakaPlayerSettings.current,
  );

  useEffect(() => {
    let isMounted = true;

    const setup = async () => {
      await initVideoPlayer();
    };

    setup();

    return () => {
      if (isMounted) {
        cleanup();
      }
      isMounted = false;
    };
  }, []);

  const initVideoPlayer = async () => {
    try {
      setIsLoading(true);
      videoPlayer.current = new VideoPlayer();
      await videoPlayer.current.initialize();

      videoPlayer.current.autoplay = false;

      shakaPlayer.current = new ShakaPlayer(
        videoPlayer.current,
        playerSettings,
      );
      console.log('✅ Shaka Player created');

      await shakaPlayer.current.load(content, true);
      console.log('✅ Shaka content loaded');

      setIsReady(true);
    } catch (err) {
      console.error('❌ Error initializing video player:', err);
    } finally {
      setIsLoading(false);
    }
  };

  // 🧹 Cleanup player
  const cleanup = async () => {
    console.log('🧹 Cleaning up player...');
    try {
      await videoPlayer.current?.deinitialize();
    } catch (err) {
      console.warn('Error cleaning up:', err);
    } finally {
      videoPlayer.current = null;
      shakaPlayer.current = null;
    }
  };

  // Surface & Caption handles
  const onSurfaceCreated = (surfaceHandle: string) => {
    console.log('Surface created:', surfaceHandle);
    videoPlayer.current?.setSurfaceHandle(surfaceHandle);
  };

  const onSurfaceDestroyed = (surfaceHandle: string) => {
    console.log('Surface destroyed:', surfaceHandle);
    videoPlayer.current?.clearSurfaceHandle(surfaceHandle);
  };

  const onCaptionCreated = (captionHandle: string) => {
    console.log('Caption created:', captionHandle);
    videoPlayer.current?.setCaptionViewHandle(captionHandle);
  };

  return (
    <View style={styles.container}>
      {isReady ? (
        <>
          <KeplerVideoSurfaceView
            style={styles.surface}
            onSurfaceViewCreated={onSurfaceCreated}
            onSurfaceViewDestroyed={onSurfaceDestroyed}
          />
          <KeplerCaptionsView
            onCaptionViewCreated={onCaptionCreated}
            style={styles.captionView}
          />
        </>
      ) : (
        <TouchableOpacity
          style={styles.playButton}
          onPress={initVideoPlayer}
          hasTVPreferredFocus={true}>
          <Text style={styles.playLabel}>Start video</Text>
        </TouchableOpacity>
      )}

      {isLoading && (
        <View style={styles.overlay}>
          <Text style={{color: 'white'}}>Loading video...</Text>
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {flex: 1, backgroundColor: 'black', justifyContent: 'center'},
  surface: {width: '100%', height: '100%'},
  captionView: {
    width: '100%',
    height: '100%',
    top: 0,
    left: 0,
    position: 'absolute',
    backgroundColor: 'transparent',
    flexDirection: 'column',
    alignItems: 'center',
    zIndex: 2,
  },
  overlay: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 10,
    backgroundColor: 'rgba(0,0,0,0.3)',
  },
  playButton: {
    backgroundColor: '#303030',
    padding: 20,
    borderRadius: 10,
    alignSelf: 'center',
  },
  playLabel: {color: '#fff', fontSize: 20},
});

//tsconfig.json
{
    "extends": "@tsconfig/react-native/tsconfig.json",
    "compilerOptions": {
        "types": ["jest", "@amazon-devices/react-native-kepler"],
        "typeRoots": [
            "src/w3cmedia/shakaplayer",
            "node_modules/@types"
        ]
    },
    "exclude": [
        "src/shakaplayer/dist"
    ]
}

Regards,
Hoang Dang

Hi @Hoang_Dang ,

I’ve reviewed your video playback issue and identified two adjustments that should resolve the black screen problem.

What I Found

  1. Missing playback trigger: Since you’ve set autoplay = false, the video loads but doesn’t start playing automatically.

  2. Video format compatibility: ShakaPlayer requires adaptive streaming formats like DASH manifests rather than direct MP4 files.

Suggested Changes

Please try these modifications:

1. Add the play() call

In your initVideoPlayer function, add this line after loading the content:

await shakaPlayer.current.load(content, true);
console.log('✅ Shaka content loaded');

// Add this line:
await shakaPlayer.current.play();
console.log('▶️ Playback started');

2. Use a compatible video format

Replace your current video URL with this DASH format example:

const content = {
  secure: false,
  uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd', // DASH format example
  drm_scheme: '',
  drm_license_uri: '',
};

Would you be able to give these changes a try? Let me know if you run into any other issues.

Regards,
Aishwarya

Hi amen
I’ve updated the code following your guidance and also checked the version of @amazon-devices/react-native-w3cmedia, which should be 2.1.0. The video is now playing successfully.
First of all, thank you for your help! I also have a few more questions so I can understand this part better:

  1. Is there a way for me to check the player’s status (whether it’s loading, has finished loading, is playing, or has ended)? I want to add a loading indicator for the video, and later I might want to trigger an action when the video finishes.

  2. How can I change the default URL — uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd' — to use an MP4 file (or other formats) instead?

  3. Regarding the video interface, can I customize the player UI, such as adding media controls to control playback?

Regards,
Hoang Dang

Hi @Hoang_Dang ,

Great to hear that your video is now playing successfully! Here are the answers to your three questions:

1. Checking Player Status

With the Vega Media Controls integration, you can use the IPlaybackState interface to monitor player status. The W3C Media API automatically handles state management when integrated with Kepler Media Controls (KMC). IPlaybackState

2. Using MP4 Files Instead of DASH

Set the video source directly using the HTMLMediaElement’s src property for MP4 files:

// For MP4 files, use HTMLMediaElement directly
const content = {
    src: 'your-domain.com/video.mp4'// Direct MP4 URL
};

// Set the source
player.src = content.src;

// Control autoplay
player.autoplay = false; // or true for automatic playback

// Start playback manually
await player.play();

The HTMLMediaElement API supports various formats including MP4, WebM, and Ogg through the src property.

@amazon-devices/react-native-w3cmedia

3. Customizing Player UI and Media Controls

The Vega Media Controls documentation shows you can override specific control commands while maintaining the automatic integration. You can customize the player controls by extending the VegaMediaControlHandler class: W3C Media integration with Vega Media Controls

Let me know if you need clarification on any of these points or run into any issues during implementation!

Best regards,
Aishwarya