HTTP Live Streaming на Android из динамически создаваемого плейлиста
Я создаю приложение для Android, которое передает потоковое видео по протоколу Http Live Streaming из веб-приложения. В настоящее время я транслирую список воспроизведения видео и сегменты, созданные Amazon Elastic Transcoder, из корзины Amazon S3. Это прекрасно работает с помощью простого VideoView и установки пути к видео по URL-адресу списка воспроизведения.m3u8 на S3.
У меня есть требования использовать 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
файл 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
Я доказал, что это будет работать с видеоплеером (VLC), который поддерживает HLS.
Я создаю правильный вариант списка воспроизведения и пытаюсь установить его в качестве пути для VideoView. Я пытался использовать как setVideoPath и setVideoURI, но безрезультатно. Единственное интересное различие в logcat между использованием динамически генерируемого списка воспроизведения и потоковой передачей непосредственно из S3 - это созданный экземпляр MediaPlayer. При потоковой передаче из локального файла (file://) создается экземпляр AwesomePlayer. При потоковой передаче из Интернета по HTTPS (https://) создается экземпляр NuPlayer. Ошибка, которую я получаю с динамически генерируемым списком воспроизведения:
ERROR/AwesomePlayer(1837): setDataSource_l() extractor is NULL, return UNKNOWN_ERROR
Потратив несколько часов на изучение исходного кода Android, я нашел свойство media.stagefright.use-nuplayer, которое заставит NuPlayer всегда использоваться, если он установлен. Тем не менее, похоже, что нет простого способа установить это собственное системное свойство из Java.
Есть ли какой-нибудь способ на прикладном уровне, которым я могу заставить NuPlayer использовать, или другой способ добиться того, чего я хочу?
Я тестирую это на Samsung Galaxy S2 под управлением Android 4.1.2.
Код:
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;
}
}