使用Shaka播放器播放自适应内容 (HLS/DASH)
以下步骤介绍如何将Shaka播放器用于MSE模式,以播放自适应流媒体内容。
尽管亚马逊提供的Shaka播放器补丁针对的是特定版本的播放器,但您可以将它们移植到您想使用的任何版本的Shaka播放器上。我们没有规定您必须使用的任何特定Shaka播放器版本。您可以决定哪个版本的Shaka播放器最适合您的要求。有关Shaka播放器处理您内容的能力的问题,请洽询开源社区。
有关Shaka播放器及其支持的不同配置的更多详细信息,请参阅Shaka播放器。
先决条件
在开始修改代码以在Shaka播放器上播放自适应内容之前,请先满足以下先决条件以设置您的应用和播放器:
- 将您的应用设置成使用W3C媒体播放器。有关更多信息,请参阅媒体播放器设置。
- 查看Shaka播放器文档,了解完整的依赖项清单。
- 添加以下内容进行更新,以启用Shaka播放器:
-
在您的应用文件夹中打开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播放器。
-
将适用于Vega的Shaka播放器下载到已知位置。
注意: 从Vega软件开发工具包 (SDK) 版本0.21开始,通过SDK提供的所有npm包的命名空间都是@amazon-devices。为了避免polyfill文件中出现编译错误,新应用和更新到V0.21的应用必须使用这个npm命名空间。Vega支持以下Shaka播放器版本。有关每个版本包含的内容的信息,请参阅发行说明(仅提供英文版):
- 4.8.5发行说明
- 4.8.5-r1.2 - 在多个播放场景中优化了性能和稳定性。在媒体处理方面进行了优化,避免对相同MIME类型进行不必要的MediaSource重置操作。修复了清单更新无限延迟的问题。在HLS字幕中,当存在X-TIMESTAMP-MAP但缺少不连续标记时,提升了时间同步精度。新增密钥系统访问克隆功能,让DRM的功能变得更稳健。将npm包的命名空间更新为
@amazon-devices。
- 4.8.5-r1.2 - 在多个播放场景中优化了性能和稳定性。在媒体处理方面进行了优化,避免对相同MIME类型进行不必要的MediaSource重置操作。修复了清单更新无限延迟的问题。在HLS字幕中,当存在X-TIMESTAMP-MAP但缺少不连续标记时,提升了时间同步精度。新增密钥系统访问克隆功能,让DRM的功能变得更稳健。将npm包的命名空间更新为
- 4.6.18发行说明
- 4.6.18-r2.15 - 在多个播放场景中优化了性能和稳定性。在媒体处理方面进行了优化,避免对相同MIME类型进行不必要的MediaSource重置操作。修复了清单更新无限延迟的问题,并修复了DASH原生化过程中的若干错误。
- 4.3.6发行说明
- 4.3.6-r2.5 - 更新了polyfill以支持Headless JS播放。已将导入路径从
@amzn/react-native-w3cmedia更改为@amazon-devices/react-native-w3cmedia/dist/headless。将npm包的命名空间更新为@amazon-devices。
- 4.3.6-r2.5 - 更新了polyfill以支持Headless JS播放。已将导入路径从
- 4.8.5发行说明
-
解压Shaka播放器程序包。
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脚本会执行编译,在脚本目录中生成一个名为shaka-player的文件夹。如果存在任何编译问题,请参阅Shaka播放器编译故障排除,以获取可能的解决方案。
- 从生成的shaka-player目录中,将shaka-rel/src/*目录中的内容复制到<应用根目录>/src/*。
- 从生成的shaka-player目录中,将dist文件夹复制到<应用根目录>/src/shakaplayer目录中。
播放自适应内容
完成以下步骤,在安装视频组件后加载Shaka播放器。
使用Shaka播放器播放自适应内容
-
打开您的src/App.tsx并用以下代码替换内容。
/* * 版权所有 (c) 2024 Amazon.com, Inc.或其关联公司。 保留所有权利。 * * 专有/机密信息。 相关使用受许可条款的约束。 */ 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 '@@amazon-devices/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播放器,请执行以下操作。
- 从https://github.com/shaka-project/shaka-player克隆Shaka程序包。
cd <根目录> git clone https://github.com/shaka-project/shaka-player.git - 基于您正在使用的Shaka播放器版本拉取相应的git分支,并创建一个名为v<x.y.z>-kepler的本地分支。将x.y.z替换为您要拉取的版本的主要、次要和补丁版本号。
cd shaka-player git checkout -b v<x.y.z>-kepler v<x.y.z> - 将与您正在使用的Shaka播放器版本相匹配的VegaShaka播放器程序包下载到一个指定位置,您将在此展开文件。
-
展开文件。
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播放器。
cd shaka-player build/all.py
Shaka播放器编译故障排除
未找到glibc的错误
如果您收到以下错误,请按如下所示设置节点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月6日

