我正在创建一个Android应用程序,它使用来自Web应用程序的Http Live Streaming协议来传输视频。目前,我正在通过Amazon S3存储桶传输由Amazon Elastic Transcoder创建的视频播放列表和片段。使用简单的VideoView并将视频路径设置为S3上的.m3u8播放列表的URL,这非常有效。
我要求使用Amazon CloudFront进行投放,并限制对存储播放列表和细分的S3存储桶的所有公共访问。根据我的research,执行此操作的唯一方法是动态生成HLS播放列表,以包含段的正确签名URL。
而不是生成播放列表并将其存储在服务器端,这需要定期清理资源,我目前尝试解决方案如下。 Android应用程序将检索流信息和媒体播放列表,以便可以在本地重新创建。
例如,我们正在播放包含两个流的视频:低质量和高质量。有关流(带宽,编解码器等)的信息将与每个流的完整HLS播放列表一起返回。这里的关键是媒体播放列表将为视频片段正确签名。 Android应用会将这些媒体播放列表写入本地临时文件并生成变体播放列表。因此本地文件系统将具有以下文件:
变体播放列表:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1280000
low.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7680000
hi.m3u8
low.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,但无济于事。使用动态生成的播放列表和直接从S3流式传输之间logcat的唯一有趣区别是创建的MediaPlayer实例。从本地文件(文件://)流式传输时,将实例化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;
}
}