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."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."typeRoots": ["src/w3cmedia/shakaplayer" , "node_modules/@types"]
-
In tsconfig.json, append "src/shakaplayer/dist" as an exclude directory.
"exclude": [ "src/shakaplayer/dist"]
-
Configure the Shaka Player for Vega
-
Download the Shaka Player for Vega to a known location.
Note: Starting with Vega Software Development Kit (SDK) version 0.21, the scope of all the npm packages provided through the SDK is@amazon-devices
. To avoid build errors in the polyfill files, new apps and apps updtating to V0.21 must use this npm scope.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.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
- 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.
- 4.3.6-r2.5 - Updated polyfills to support Headless JS playback. Changed imports from
- 4.8.5 release notes
-
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
- Navigate to the /shaka-rel/scripts directory.
- 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.
- From the generated shaka-player dirctory, copy the contents of the shaka-rel/src/* directory to <app root>/src/*.
- 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.
/*
* 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.
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
totrue
as shown in the following example.
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
- Clone the Shaka package from https://github.com/shaka-project/shaka-player.
cd <root dir> git clone https://github.com/shaka-project/shaka-player.git
- 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.
cd shaka-player git checkout -b v<x.y.z>-kepler v<x.y.z>
</div>
- 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.
-
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
- Apply the Shaka patches.
git am shaka-rel/shaka-patch/*.patch -3
- Build Shaka Player by running build/all.py.
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
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'.
Related topics
- W3C Media Player Overview
- Play adaptive content using Dash.js Player
- Play adaptive content using Hls.js Player
- Media Player playlist playback
Last updated: Sep 30, 2025