Shaka Playerを使用したアダプティブコンテンツ(HLS/DASH)の再生
次の手順では、Shaka PlayerをMSEモードで使用して、アダプティブストリーミングコンテンツを再生する方法を示しています。
Amazonから提供されるShaka Playerパッチは特定のバージョンのプレーヤーを対象としていますが、任意のバージョンのShaka Playerに移植することができます。Amazonでは、特定のバージョンのShaka Playerを使用するように要求する規定は設けていません。開発者は、独自の要件に合わせて最適なShaka Playerのバージョンを決定できます。Shaka Playerによるコンテンツの処理に関する問題については、オープンソースコミュニティに問い合わせてください。
Shaka Playerとサポートされるさまざまな構成の詳細については、Shaka Player(英語のみ)を参照してください。
前提条件
Shaka Playerでアダプティブコンテンツを再生できるようにコードを変更する前に、次の前提条件を満たしていることを確認し、アプリとプレーヤーを設定します。
- W3Cメディアプレーヤーを使用するようにアプリを設定します。詳細については、メディアプレーヤーのセットアップを参照してください。
- 依存関係の完全なリストについては、Shaka Playerのドキュメントを確認してください。
- Shaka Playerを有効にするには、追加で以下の更新を行います。
-
appフォルダのpackage.jsonを開きます。
dependenciesセクションに、次の依存関係を追加します。"xmldom": "0.6.0", "base-64": "1.0.0", "fastestsmallesttextencoderdecoder": "1.0.22" -
tsconfig.jsonファイルを開き、
typeRootsを見つけます。値を次のように設定します。"typeRoots": ["src/w3cmedia/shakaplayer" , "node_modules/@types"] -
tsconfig.jsonで、"src/shakaplayer/dist"を除外ディレクトリに追加します。
"exclude": [ "src/shakaplayer/dist"]
-
Vega向けShaka Playerの構成
-
Vega向けShaka Playerを既知の場所にダウンロードします。
注: Vegaソフトウェア開発キット(SDK)バージョン0.21以降、SDKを通じて提供されるすべてのnpmパッケージの範囲は@amazon-devicesになります。ポリフィルファイルのビルドエラーを回避するには、新しいアプリとV0.21に更新するアプリは、このnpmスコープを使用する必要があります。VegaがサポートしているShake Playerのバージョンは、以下のとおりです。各バージョンの内容については、リリースノートを参照してください。
- 4.8.5に関するリリースノート(英語のみ)
- 4.8.5-r1.2 - 複数の再生シナリオにわたってパフォーマンスと安定性が向上しました。同じMIMEタイプでMediaSourceが不必要にリセットされないようにすることで、メディア処理を強化しました。マニフェストの更新が無限に遅延する問題を解決しました。X-TIMESTAMP-MAPが不連続マーカーなしで存在する場合の、HLS字幕のタイミング精度が向上しました。より強固なDRM機能を実現するために、主要なシステムアクセスのクローニング機能が追加されました。npmパッケージのスコープが
@amazon-devicesに更新されました。 - 4.8.5-r1.0
- 4.8.5-r1.2 - 複数の再生シナリオにわたってパフォーマンスと安定性が向上しました。同じMIMEタイプでMediaSourceが不必要にリセットされないようにすることで、メディア処理を強化しました。マニフェストの更新が無限に遅延する問題を解決しました。X-TIMESTAMP-MAPが不連続マーカーなしで存在する場合の、HLS字幕のタイミング精度が向上しました。より強固なDRM機能を実現するために、主要なシステムアクセスのクローニング機能が追加されました。npmパッケージのスコープが
- 4.6.18に関するリリースノート(英語のみ)
- 4.6.18-r2.15 - 複数の再生シナリオにわたってパフォーマンスと安定性が向上しました。同じMIMEタイプでMediaSourceが不必要にリセットされないようにすることで、メディア処理を強化しました。マニフェストの更新が無限に遅延する問題を修正し、DASHネイティブ化のバグを修正しました。
- 4.6.18-r2.13 - 解析エラーによりネイティブ化が有効になっているときに字幕が表示されない問題の修正を追加しました。
- 4.6.18-r2.12 - ヘッドレスJS再生をサポートするようにポリフィルを更新しました。インポートを
@amzn/react-native-w3cmediaから@amzn/react-native-w3cmedia/dist/headlessに変更しました。 - 4.6.18-r2.11 - HLSネイティブ化のクラッシュを修正しました。decodingInfo API用のポリフィルを提供します。
- 4.6.18-r2.8 - Shaka Playerのdestroyメソッドの実行時にTypeErrorが発生しないようにする
window.removeEventListenerのポリフィルを追加しました。 - 4.6.18-r2.7
- 4.3.6に関するリリースノート(英語のみ)
- 4.3.6-r2.5 - ヘッドレスJS再生をサポートするようにポリフィルを更新しました。インポートを
@amzn/react-native-w3cmediaから@amzn/react-native-w3cmedia/dist/headlessに変更しました。npmパッケージのスコープが@amazon-devicesに更新されました。 - 4.3.6-r2.4 - HLSネイティブ化のクラッシュを修正しました。
- 4.3.6-r2.1 - Shaka Playerのdestroyメソッドの実行時にTypeErrorが発生しないようにする
window.removeEventListenerのポリフィルを追加しました。 - 4.3.6-r2.0 - オーディオフォーカス管理用のカスタムイベントの処理を追加しました。
- 4.3.6-r2.5 - ヘッドレスJS再生をサポートするようにポリフィルを更新しました。インポートを
- 4.8.5に関するリリースノート(英語のみ)
-
Shaka Playerパッケージを展開します。
tar -xzf shaka-rel-v<x.y.z>-r<a.b>.tar.gz次に例を示します。
tar -xzf shaka-rel-v4.6.18-r2.12.tar.gz - /shaka-rel/scriptsディレクトリに移動します。
- setup.shヘルパースクリプトを実行します。
./setup.sh
setup.shスクリプトはビルドを実行して、scriptsディレクトリにshaka-playerという名前のフォルダを生成します。ビルドの問題がある場合に考えられる解決策については、Shaka Playerビルドのトラブルシューティングを参照してください。
- 生成されたshaka-playerディレクトリの下にあるshaka-rel/src/*ディレクトリの内容を<アプリのルート>/src/*にコピーします。
- 生成されたshaka-playerディレクトリの下にあるdistフォルダを <アプリのルート>/src/shakaplayerディレクトリにコピーします。
アダプティブコンテンツの再生
ビデオコンポーネントがマウントされたら、次の手順を実行してShaka Playerを読み込みます。
Shaka Playerでアダプティブコンテンツを再生する方法
- src/App.tsxを開き、コンテンツを以下のコードに置き換えます。
/*
* Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All rights reserved.
*
* 専有/機密情報。 使用にあたってはライセンス条項が適用されます。
*/
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';
// アプリからビデオの再生APIを手動で呼び出す場合は、falseに設定します。
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: セキュアなビデオバッファーを使用します。false: 非セキュアなビデオバッファーを使用します。
uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd',
drm_scheme: '', // com.microsoft.playready, com.widevine.alpha
drm_license_uri: '', // DRMライセンス取得サーバーのURL(コンテンツがDRMで保護されている場合にのみ必要)
},
];
export const App = () => {
const currShakaPlayerSettings = useRef<ShakaPlayerSettings>({
secure: false, // 再生をセキュアモードまたは非セキュアモードで実行
abrEnabled: true, // アダプティブビットレート(ABR)の切り替えを有効化
abrMaxWidth: DEFAULT_ABR_WIDTH, // ABRで許容される最大幅
abrMaxHeight: DEFAULT_ABR_HEIGHT, // 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 }
// 再レンダリングのためにnextContentの状態を追跡します。
const nextContentRef = useRef<number>(0);
// 全画面解像度でレンダリングします。
const {width: deviceWidth, height: deviceHeight} = useWindowDimensions();
useEffect(() => {
if (nextContent.index !== nextContentRef.current) {
nextContentRef.current = nextContent.index;
// <Video>コンポーネントを強制的に再レンダリングします。
initializeVideoPlayer();
setNextContent((prev) => {
return {...prev};
});
}
}, [nextContent]);
useEffect(() => {
console.log('app:AppPreBuffering v13.0を開始します');
initializeVideoPlayer();
}, []);
const onEnded = async () => {
console.log('app:onEndedを受け取りました');
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:エラーイベントリスナーが呼び出されました`);
};
const setUpEventListeners = (): void => {
console.log('app:イベントリスナーをセットアップします');
videoPlayer.current?.addEventListener('ended', onEnded);
videoPlayer.current?.addEventListener('error', onError);
};
const removeEventListeners = (): void => {
console.log('app:イベントリスナーを削除します');
videoPlayer.current?.removeEventListener('ended', onEnded);
videoPlayer.current?.removeEventListener('error', onError);
};
const initializeVideoPlayer = async () => {
console.log('app: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:サーフェスが作成されました');
videoPlayer.current?.setSurfaceHandle(surfaceHandle);
videoPlayer.current?.play();
};
const onSurfaceViewDestroyed = (surfaceHandle: string): void => {
videoPlayer.current?.clearSurfaceHandle(surfaceHandle);
};
const onCaptionViewCreated = (captionsHandle: string): void => {
console.log('app:キャプションビューが作成されました');
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}>ここを押してビデオを再生</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,
}
});
アプリをビルドし、デバイスまたはシミュレーターで実行してログを収集するには、次のコマンドを実行します。
kepler run build:app
アプリを作成してシミュレーターで実行する方法の詳細については、Vegaアプリの作成およびVega仮想デバイスを参照してください。
- MPEGTSコンテンツを含むHLSアダプティブストリームに対してアプリでシーケンスモードを有効にする必要がある場合は、次の例に示すように、
manifest.hls.sequenceModeをtrueに設定することをお勧めします。
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);
}
};
この関数は、HLSストリームでシーケンスモードを有効にします。シーケンスモードでは、セグメントのタイムスタンプに関係なく隣接するセグメントを再生できます。これは、メディアセグメントのタイムスタンプの順序が乱れていて、メディアパイプラインが停止する可能性がある場合に役立ちます。
Shaka Playerを手動で統合する方法
- https://github.com/shaka-project/shaka-playerからShakaパッケージをクローンします。
cd <ルートディレクトリ> git clone https://github.com/shaka-project/shaka-player.git - 使用しているShaka Playerのバージョンに対応したgitブランチを、v<x.y.z>keplerという名前のローカルブランチにチェックアウトします。x.y.z を、チェックアウトするバージョンのメジャー・マイナー・パッチバージョン番号に置き換えます。
cd shaka-player git checkout -b v<x.y.z>-kepler v<x.y.z>
</div>
- 使用しているShaka Playerのバージョンと一致するVega Shaka Playerパッケージファイルを、展開先となる既知の場所にダウンロードします。
-
ファイルを展開します。
tar -xzf shaka-rel-v<x.y.z>-r<a.b>.tar.gz次に例を示します。
tar -xzf shaka-rel-v4.6.18-r2.12.tar.gz - Shakaパッチを適用します。
git am shaka-rel/shaka-patch/*.patch -3 - build/all.pyを実行して、Shaka Playerをビルドします。
cd shaka-player build/all.py
Shaka Playerビルドのトラブルシューティング
glibcが見つからないエラー
次のエラーが発生する場合は、後に示す方法でnode 16.20.2をセットアップします。
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 <モジュール>
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
nvm 16.20.2のセットアップ
nvm use 16.20.2
ESLintエラー?
どの親ディレクトリにも.eslitnrc.jsファイルがないことを確認します。
[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.
ビルドが完了しない
package-lock.jsonファイルを編集し、「git+ssh」という文字列の出現箇所をすべて「https」に置き換えます。
関連トピック
Last updated: 2025年10月1日

