as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
AWS
Documentation
Support
Contact Us
My Cases
Get Started
Design and Develop
Publish
Reference
Support

Play adaptive content (HLS/DASH) with Shaka Player

The following steps show you how to use Shaka Player with MSE mode to play adaptive streaming content.

Even though the Shaka Player patches provided by Amazon are for a specific version of the player, you can port them to any version of Shaka Player that you want to use. We don't prescribe any specific version of Shaka Player that you must use. You can decide which version of Shaka Player is most suitable for your requirement. For issues related to Shaka Player's ability to handle your content engage with the open source community.

For more details about the Shaka Player and the different configurations it supports, see Shaka Player.

Prerequisites

Before you begin to modify your code to play adaptive content on the Shaka Player, complete the following prerequisites to set up your app and the player:

  • Set up your app to use the W3C Media Player. For more information, see Media Player Setup.
  • Review the Shaka Player documentation for a full list of dependencies.
  • Make the following additional updates to enable the Shaka Player:
    • Open the package.json in your app folder. In the dependencies section, include the following dependencies.

      Copied to clipboard.

      "xmldom": "0.6.0",
      "base-64": "1.0.0",
      "fastestsmallesttextencoderdecoder": "1.0.22"
      
    • Open the tsconfig.json file and find typeRoots. Set the value to the following.

      Copied to clipboard.

      "typeRoots": ["src/w3cmedia/shakaplayer" , "node_modules/@types"]
      
    • In tsconfig.json, append "src/shakaplayer/dist" as an exclude directory.

      Copied to clipboard.

      "exclude": [ "src/shakaplayer/dist"]
      

Configure the Shaka Player for Vega

  1. Download the Shaka Player for Vega to a known location.

    Vega supports the following Shake Player versions. For information about what each version contains, refer to the release notes:

    • 4.8.5 release notes
      • 4.8.5-r1.2 - Performance and stability improvements implemented across multiple playback scenarios. Enhanced media handling by preventing unnecessary MediaSource resets for identical MIME types. Resolved an issue with infinite manifest update delays. Improved HLS subtitle timing accuracy when X-TIMESTAMP-MAP is present without discontinuity markers. Added functionality for key system access cloning for more robust DRM functionality. Scope for the npm packages updated to @amazon-devices.
      • 4.8.5-r1.0
    • 4.6.18 release notes
      • 4.6.18-r2.15 - Performance and stability improvements implemented across multiple playback scenarios. Enhanced media handling by preventing unnecessary MediaSource resets for identical MIME types. Resolved an issue with infinite manifest update delays and bug fixes in DASH nativization.
      • 4.6.18-r2.13 - Added fix for the issue of subtitles not showing when nativization is enabled due to parsing error.
      • 4.6.18-r2.12 - Updated polyfills to support Headless JS playback. Changed imports from @amzn/react-native-w3cmedia to @amzn/react-native-w3cmedia/dist/headless.
      • 4.6.18-r2.11 - Fixed HLS nativisation crash. Provides polyfills for the decodingInfo API.
      • 4.6.18-r2.8 - Added the window.removeEventListener polyfill to avoid a TypeError when the Shaka Player destroy method is run.
      • 4.6.18-r2.7
    • 4.3.6 release notes
      • 4.3.6-r2.5 - Updated polyfills to support Headless JS playback. Changed imports from @amzn/react-native-w3cmedia to @amzn/react-native-w3cmedia/dist/headless. Scope for the npm packages updated to @amazon-devices.
      • 4.3.6-r2.4 - Fixed HLS nativisation crash.
      • 4.3.6-r2.1 - Added the window.removeEventListener polyfill to avoid a TypeError when the Shaka Player destroy method is run.
      • 4.3.6-r2.0 - Added handling of custom events for audio focus management.
  2. Expand the Shaka Player package.

    tar -xzf shaka-rel-v<x.y.z>-r<a.b>.tar.gz

    For example.

    tar -xzf shaka-rel-v4.6.18-r2.12.tar.gz

  3. Navigate to the /shaka-rel/scripts directory.
  4. Run the setup.sh helper script.
    ./setup.sh

The setup.sh script performs a build which generates a folder in the scripts directory named shaka-player. If there are any build issues, see Troubleshooting Shaka Player build for possible solutions.

  1. From the generated shaka-player dirctory, copy the contents of the shaka-rel/src/* directory to <app root>/src/*.
  2. From the generated shaka-player dirctory, copy the dist folder to the <app root>/src/shakaplayer directory.

Play adaptive content

Complete the following steps to load the Shaka Player when the Video component is mounted.

To play adaptive content with Shaka Player

  • Open your src/App.tsx and replace the contents with following code.

Copied to clipboard.


/*
* Copyright (c) 2024 Amazon.com, Inc. or its affiliates.  All rights reserved.
*
* PROPRIETARY/CONFIDENTIAL.  USE IS SUBJECT TO LICENSE TERMS.
*/

import * as React from 'react';
import {useRef, useState, useEffect} from 'react';
import {
  Platform,
  useWindowDimensions,
  View,
  StyleSheet,
  TouchableOpacity,
  Text,
} from 'react-native';

import {
  VideoPlayer,
  KeplerVideoSurfaceView,
  KeplerCaptionsView,
} from '@amzn/react-native-w3cmedia';
import {ShakaPlayer, ShakaPlayerSettings} from './shakaplayer/ShakaPlayer';

// set to false if app wants to call play API on video manually
const AUTOPLAY = true;

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

const content = [
  {
    secure: 'false', // true : Use Secure Video Buffers. false: Use Unsecure Video Buffers.
    uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd',
    drm_scheme: '', // com.microsoft.playready, com.widevine.alpha
    drm_license_uri: '', // DRM License acquisition server URL : needed only if the content is DRM protected
  },
];

export const App = () => {
  const currShakaPlayerSettings = useRef<ShakaPlayerSettings>({
    secure: false, // Playback goes through secure or non-secure mode
    abrEnabled: true, // Enables Adaptive Bit-Rate (ABR) switching
    abrMaxWidth: DEFAULT_ABR_WIDTH, // Maximum width allowed for ABR
    abrMaxHeight: DEFAULT_ABR_HEIGHT, // Maximum height allowed for ABR
  });

  const player = useRef<any>(null);
  const videoPlayer = useRef<VideoPlayer | null>(null);
  const timeoutHandler = useRef<ReturnType<typeof setTimeout> | null>(null);
  const [playerSettings, setPlayerSettings] = useState<ShakaPlayerSettings>(
    currShakaPlayerSettings.current,
  );
  const [buttonPress, setButtonPress] = useState(false);
  const [nextContent, setNextContent] = useState({index: 0}); // { index: number }
  // Track the nextContent state for re-rendering
  const nextContentRef = useRef<number>(0);
  // Render in Full screen resolution
  const {width: deviceWidth, height: deviceHeight} = useWindowDimensions();

  useEffect(() => {
    if (nextContent.index !== nextContentRef.current) {
      nextContentRef.current = nextContent.index;
      // Force Re-rendering of <Video> component.
      initializeVideoPlayer();
      setNextContent((prev) => {
        return {...prev};
      });
    }
  }, [nextContent]);

  useEffect(() => {
    console.log('app:  start AppPreBuffering v13.0');
    initializeVideoPlayer();
  }, []);

  const onEnded = async () => {
    console.log('app: onEnded received');
    player.current.unload();
    player.current = null;
    await videoPlayer.current?.deinitialize();
    removeEventListeners();
    onVideoUnMounted();
    setNextContent({index: (nextContent.index + 1) % content.length});
  };

  const onError = () => {
    console.log(`app: AppPreBuffering: error event listener called`);
  };

  const setUpEventListeners = (): void => {
    console.log('app: setup event listeners');
    videoPlayer.current?.addEventListener('ended', onEnded);
    videoPlayer.current?.addEventListener('error', onError);
  };

  const removeEventListeners = (): void => {
    console.log('app: remove event listeners');
    videoPlayer.current?.removeEventListener('ended', onEnded);
    videoPlayer.current?.removeEventListener('error', onError);
  };

  const initializeVideoPlayer = async () => {
    console.log('app: calling initializeVideoPlayer');
    videoPlayer.current = new VideoPlayer();
    // @ts-ignore
    global.gmedia = videoPlayer.current;
    await videoPlayer.current.initialize();
    setUpEventListeners();
    videoPlayer.current!.autoplay = false;
    initializeShaka();
  };

  const onSurfaceViewCreated = (surfaceHandle: string): void => {
    console.log('app: surface created');
    videoPlayer.current?.setSurfaceHandle(surfaceHandle);
    videoPlayer.current?.play();
  };

  const onSurfaceViewDestroyed = (surfaceHandle: string): void => {
    videoPlayer.current?.clearSurfaceHandle(surfaceHandle);
  };

  const onCaptionViewCreated = (captionsHandle: string): void => {
    console.log('app: caption view created');
    videoPlayer.current?.setCaptionViewHandle(captionsHandle);
  };

  const initializeShaka = () => {
    console.log('app: in initializePlayer() index = ', nextContent.index);
    if (videoPlayer.current !== null) {
      player.current = new ShakaPlayer(videoPlayer.current, playerSettings);
    }
    if (player.current !== null) {
      player.current.load(content[nextContent.index], AUTOPLAY);
    }
  };

  const onVideoUnMounted = (): void => {
    console.log('app: in onVideoUnMounted');
    // @ts-ignore
    global.gmedia = null;
    videoPlayer.current = null;
  };

  if (!buttonPress) {
    return (
      <View style={styles.container}>
        <TouchableOpacity
          style={styles.button}
          onPress={() => {
            setButtonPress(true);
          }}
          hasTVPreferredFocus={true}
          activeOpacity={1}>
          <Text style={styles.buttonLabel}> Press to Play Video </Text>
        </TouchableOpacity>
      </View>
    );
  } else {
    return nextContent.index === nextContentRef.current ? (
      <View style={styles.videoContainer}>
        <VegaVideoSurfaceView
          style={styles.surfaceView}
          onSurfaceViewCreated={onSurfaceViewCreated}
          onSurfaceViewDestroyed={onSurfaceViewDestroyed}
        />
        <VegaCaptionsView
          onCaptionViewCreated={onCaptionViewCreated}
          style={styles.captionView}
        />
      </View>
    ) : (
      <View style={styles.videoContainer}></View>
    );
  }
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    backgroundColor: '#283593',
    justifyContent: 'center',
    alignItems: 'center',
  },
  button: {
    alignItems: 'center',
    backgroundColor: '#303030',
    borderColor: 'navy',
    borderRadius: 10,
    borderWidth: 1,
    paddingVertical: 12,
    paddingHorizontal: 32,
  },
  buttonLabel: {
    color: 'white',
    fontSize: 22,
    fontFamily: 'Amazon Ember',
  },
  videoContainer: {
    backgroundColor: 'white',
    alignItems: 'stretch',
  },
  surfaceView: {
    zIndex: 0,
  },
  captionView: {
    width: '100%',
    height: '100%',
    top: 0,
    left: 0,
    position: 'absolute',
    backgroundColor: 'transparent',
    flexDirection: 'column',
    alignItems: 'center',
    zIndex: 2,
  }
});

To build the app, run it on a device or the simulator, and then collect the logs, run the follwing command.

Copied to clipboard.

kepler run build:app

For more details about building and running apps on the simulator, see Create a Vega App and Vega Virtual Device.

  • If you need to enable sequence mode for HLS adaptive streams with MPEGTS content in your app, we recommend that you set manifest.hls.sequenceMode to true as shown in the following example.

Copied to clipboard.

const initializeShaka = () => {
  console.log('app: in initializePlayer() index = ', nextContent.index);
  if (videoPlayer.current !== null) {
    player.current = new ShakaPlayer(videoPlayer.current, playerSettings);
  }
  if (player.current !== null) {
      player.current.load(content[nextContent.index], AUTOPLAY);
      player.current.player.configure('manifest.hls.sequenceMode', true);
  }
};

This function enables Sequence Mode for HLS streams. Sequence mode allows playback of adjacent segments independent of the segments timestamps. This is useful where media segment timestamps are out of order and can cause a stall in the media pipeline.

To integrate the Shaka Player manually

  1. Clone the Shaka package from https://github.com/shaka-project/shaka-player.

    Copied to clipboard.

    cd <root dir>
    git clone https://github.com/shaka-project/shaka-player.git
    
  2. Checkout the git branch based for the version of Shaka Player that you are using to a local branch named v<x.y.z>-kepler. Replace the x.y.z with the major, minor, and patch version numbers of the version that you are checkin out.

    Copied to clipboard.

    cd shaka-player
    git checkout -b v<x.y.z>-kepler v<x.y.z>
    

</div>

  1. Download a Vega Shaka Player package that matches the version of Shaka Player you are using to a known location where you will expand the file.
  2. Expand the file.

    tar -xzf shaka-rel-v<x.y.z>-r<a.b>.tar.gz

    For example.

    tar -xzf shaka-rel-v4.6.18-r2.12.tar.gz

  3. Apply the Shaka patches.

    Copied to clipboard.

    git am shaka-rel/shaka-patch/*.patch -3
    
  4. Build Shaka Player by running build/all.py.

    Copied to clipboard.

     cd shaka-player
     build/all.py
    

Troubleshooting Shaka Player build

glibc not found error

If you recieve the following error, setup node 16.20.2 as shown below.

node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by node)
Traceback (most recent call last):
  File "build/all.py", line 150, in <module>
    shakaBuildHelpers.run_main(main)
  File "/local/home/rinoshs/prj/bz/tdpws/src/ReactNativeAmznW3CMedia-ShakaPlayer/tmp/shaka-rel/scripts/shaka-player/build/shakaBuildHelpers.py", line 350, in run_main
    sys.exit(main(sys.argv[1:]))
  File "build/all.py", line 95, in main
    if gendeps.main([]) != 0:
  File "/local/home/rinoshs/prj/bz/tdpws/src/ReactNativeAmznW3CMedia-ShakaPlayer/tmp/shaka-rel/scripts/shaka-player/build/gendeps.py", line 30, in main
    if not shakaBuildHelpers.update_node_modules():
  File "/local/home/rinoshs/prj/bz/tdpws/src/ReactNativeAmznW3CMedia-ShakaPlayer/tmp/shaka-rel/scripts/shaka-player/build/shakaBuildHelpers.py", line 330, in update_node_modules
    execute_get_output(['npm', 'ci'])
  File "/local/home/rinoshs/prj/bz/tdpws/src/ReactNativeAmznW3CMedia-ShakaPlayer/tmp/shaka-rel/scripts/shaka-player/build/shakaBuildHelpers.py", line 170, in execute_get_output
    raise subprocess.CalledProcessError(obj.returncode, args[0], stdout)
subprocess.CalledProcessError: Command 'npm' returned non-zero exit status 1

Setup nvm 16.20.2

Copied to clipboard.

nvm use 16.20.2

ESLint error?

Make sure that no parent directory has a .eslitnrc.js file present.

[INFO] Generating Closure dependencies...
[INFO] Linting JavaScript...

Oops! Something went wrong! :(

ESLint: 8.9.0

ESLint couldn't find the config "google" to extend from. Please check that the name of the config is correct.

Build doesn't complete

Edit the package-lock.json file and replace all instances of string 'git+ssh' with 'https'.


Last updated: Sep 30, 2025