我正在创建一个 Android 应用程序,该应用程序使用来自 Web 应用程序的 Http Live Streaming 协议流式传输视频。目前,我正在从 Amazon S3 存储桶流式传输由 Amazon Elastic Transcoder 创建的视频播放列表和片段。使用简单的 VideoView 并将视频路径设置为 S3 上 .m3u8 播放列表的 URL 可以完美地工作。
我需要使用 Amazon CloudFront 进行交付并限制对存储播放列表和分段的 S3 存储桶的所有公共访问。根据我的研究,这样做的唯一方法是动态生成 HLS 播放列表以包含正确签名的片段 URL。
我目前的解决方案尝试如下,而不是生成播放列表并将其存储在服务器端,这需要定期清理资源。Android 应用程序将检索流信息和媒体播放列表,以便可以在本地重新创建它们。
例如,我们正在流式传输具有两个流的视频:低质量和高质量。有关流的信息(带宽、编解码器等)将与每个流的完整 HLS 播放列表一起返回。这里的关键是媒体播放列表将具有正确签名的视频片段 URL。Android 应用程序会将这些媒体播放列表写入本地临时文件并生成变体播放列表。因此本地文件系统将具有以下文件:
变体播放列表:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
hi.m3u8
低.m3u8 文件:
#EXTM3U
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:8,
https://priv.example.com/fileSequence2680-low.ts
#EXTINF:8,
https://priv.example.com/fileSequence2681-low.ts
#EXTINF:8,
https://priv.example.com/fileSequence2682-low.ts
hi.m3u8 文件:
#EXTM3U
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:8,
https://priv.example.com/fileSequence2680-hi.ts
#EXTINF:8,
https://priv.example.com/fileSequence2681-hi.ts
#EXTINF:8,
https://priv.example.com/fileSequence2682-hi.ts
我已经证明这将适用于支持 HLS 的视频播放器 (VLC)。
我正在生成一个适当的变体播放列表并尝试将其设置为 VideoView 的路径。我尝试同时使用 setVideoPath 和 setVideoURI 但无济于事。logcat 中使用动态生成的播放列表和直接从 S3 流式传输之间唯一有趣的区别是创建的 MediaPlayer 实例。当从本地文件 (file://) 流式传输时,AwesomePlayer 被实例化。当通过 HTTPS (https://) 从 Internet 流式传输时,NuPlayer 被实例化。我使用动态生成的播放列表得到的错误是:
ERROR/AwesomePlayer(1837): setDataSource_l() extractor is NULL, return UNKNOWN_ERROR
在花了几个小时挖掘 Android 源代码后,我发现了一个名为 media.stagefright.use-nuplayer 的属性,如果设置,它将强制 NuPlayer 始终使用。但是,似乎没有一种干净的方法可以从 Java 设置此本机系统属性。
在应用程序层有什么方法可以强制使用 NuPlayer 或其他方式来实现我想要的吗?
我正在运行 Android 4.1.2 的三星 Galaxy S2 上进行测试。
代码:
public class VideoPlayerActivity extends Activity
{
private static final String TAG = VideoPlayerActivity.class.getCanonicalName();
// URL intentionally changed to something generic to hide internal resources
private static final String BASE_URL = "https://s3.amazonaws.com/bucket/";
private static final String VARIANT_PLAYLIST_PATH = BASE_URL + "variant.m3u8";
private static final String MEDIA_PLAYLIST_PATH = BASE_URL + "media1.m3u8";
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
File variantPlaylist = createVariantPlaylist();
final VideoView videoView = (VideoView) findViewById(R.id.videoView);
videoView.setVideoPath(variantPlaylist.getAbsolutePath());
MediaController mediaController = new MediaController(this);
mediaController.setAnchorView(videoView);
videoView.setMediaController(mediaController);
videoView.setOnPreparedListener( new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.i(TAG, "Duration = " + videoView.getDuration());
}
});
videoView.start();
}
private File createVariantPlaylist() {
File variantPlaylist = null;
try {
File directory = this.getFilesDir();
variantPlaylist = File.createTempFile("variant", ".m3u8", directory);
FileWriter fileWriter = new FileWriter(variantPlaylist.getAbsoluteFile());
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write("#EXTM3U");
bufferedWriter.write(LINE_SEPARATOR);
bufferedWriter.write("#EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=400x170,CODECS=\"avc1.42001e,mp4a.40.2\",BANDWIDTH=474000");
bufferedWriter.write(LINE_SEPARATOR);
bufferedWriter.write(MEDIA_PLAYLIST_PATH);
bufferedWriter.close();
}
catch (IOException ioe) {
Log.e(TAG, "Error occurred creating variant playlist");
Log.e(TAG, ioe.getMessage());
}
return variantPlaylist;
}
}