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;
    }
}

0 ответов

Другие вопросы по тегам