Почему мой Exoplayer2 CacheDataSource не записывает загруженный поток в назначенный кеш?

Я реализовал собственный кеш для Exoplayer2 - и перехожу с 2.11.x на 2.12.3. Я просмотрел несколько сообщений здесь, посвященных SO (например, здесь), а также различные части из последние версии документации разработчика Exoplayer2 (например, здесь). Я могу получить потоковое видео из локального или удаленного источника (например, Dropbox), и оно воспроизводится, но я не вижу, чтобы в папке, которую я назначил в качестве моего кеша, сохраняется что-либо, кроме двух файлов (и оба файла очень маленький):

5eedc471fe18491e.uid
cached_content_index.exi

Я предполагаю, что файл .exi - это индекс, который должен отслеживать сегменты кеша, а файл .uid - это сегмент, но он всегда равен 0 байтам. Так что по какой-то причине Exoplayer распознает расположение кеша, но ничего не передает в него.

В своем приложении я делаю так:

MediaSource mediaSource = getCachedMediaSourceFactory(context, listener).createMediaSource(uri);
exoPlayer.setMediaSource(mediaSource);
exoPlayer.setPlayWhenReady(true);
exoPlayer.prepare();

getCachedMediaSourceFactory () в свою очередь выглядит так:

private static ProgressiveMediaSource.Factory getCachedMediaSourceFactory(Context context, CacheDataSource.EventListener listener) {
    if ( cachedMediaSourceFactory == null ) {
        cachedMediaSourceFactory = new ProgressiveMediaSource.Factory(
                new InTouchCacheDataSourceFactory(context,
                        DEFAULT_MEDIA_CACHE_FRAGMENT_SIZE_IN_BYTES,
                        listener));
    }
    return cachedMediaSourceFactory;
}

InTouchCacheDataSourceFactory, в свою очередь, выглядит так:

public class InTouchCacheDataSourceFactory implements DataSource.Factory {
    private final DefaultDataSourceFactory defaultDatasourceFactory;
    private final CacheDataSource.EventListener listener;
    private final long maxCacheFragmentSize;

    public InTouchCacheDataSourceFactory(Context context, long maxCacheFragmentSize) {
        this(context, maxCacheFragmentSize, null);
    }

    public InTouchCacheDataSourceFactory(Context context, long maxCacheFragmentSize, CacheDataSource.EventListener listener) {
        this.maxCacheFragmentSize = maxCacheFragmentSize;
        this.listener = listener;

        defaultDatasourceFactory =
                new DefaultDataSourceFactory(context,
                                             new DefaultHttpDataSourceFactory(Util.getUserAgent(context, APP_NAME)));

    }

    @Override
    public DataSource createDataSource() {
        return new CacheDataSource(InTouchUtils.getMediaCache(),
                                   defaultDatasourceFactory.createDataSource(),
                                   new FileDataSource(),
                                   new CacheDataSink(InTouchUtils.getMediaCache(), maxCacheFragmentSize),
                             CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
                                   listener);
    }
}

А InTouchUtils.getMediaCache выглядит так:

public static SimpleCache getMediaCache() {
    if ( mediaCache == null ) {
        Context context = InTouch.getInstance().getApplicationContext();
        long requestedCacheSize = Long.parseLong(PreferenceManager.getDefaultSharedPreferences(context).getString(context.getString(R.string.pref_default_cache_size_key), String.valueOf(DEFAULT_MEDIA_CACHE_SIZE_IN_MB)));
        File contentDirectory = new File(context.getCacheDir(), MEDIA_CACHE_DIRECTORY);
        mediaCache = new SimpleCache(contentDirectory, new LeastRecentlyUsedCacheEvictor(requestedCacheSize*(1024*1024)));
    }
    return mediaCache;
}

Опять же, видео воспроизводится нормально, но, похоже, на самом деле потоковый файл не записывается в кеш. Почему нет?

Я также реализовал интерфейс CacheDataSource.EventListener в своем приложении и переопределил эти два метода:

@Override
public void onCachedBytesRead(long cacheSizeBytes, long cachedBytesRead) {
    Timber.d("Lifecycle: Cache read, size in bytes is: %d, bytes read was: %d", cacheSizeBytes, cachedBytesRead);
}

@Override
public void onCacheIgnored(int reason) {
    switch (reason) {
        case CacheDataSource.CACHE_IGNORED_REASON_ERROR:
            Timber.d("Lifecycle: Cache ignored due to error");
            break;
        case CacheDataSource.CACHE_IGNORED_REASON_UNSET_LENGTH:
            Timber.d("Lifecycle: Cache ignored due to unset length");
            break;
    }
}

Я не вижу вызова ни одного из методов.


person tfrysinger    schedule 21.11.2020    source источник


Ответы (1)


ОБНОВЛЕНИЕ, май 2021 г.

Я создал образец приложения на GitHub, которое показывает работу этого кэшированного источника данных для всех читателей, которые интересно это увидеть.

Нажатие на верхнюю кнопку в приложении запускает средство выбора на основе SAF для выбора локального видео (поэтому кеш не требуется и не используется). Uri выбранного файла отображается в EditText, и вы можете нажать «Воспроизвести видео», чтобы просмотреть его.

Однако по умолчанию образец Uri в EditText находится в Интернете, и, поскольку это не локальный Uri (семантика которого не является чем-то действительно официальным, просто простое разграничение, которое я создал для целей этого тестового приложения), он использует настраиваемая фабрика источников данных кеша, которая запускает механику кеширования Exoplayer.

Вы можете просмотреть различия в LogCat во время работы приложения и увидеть, что после первого воспроизведения видео, передаваемого из Интернета, при втором запуске вы начинаете получать обращения из кеша, о чем свидетельствуют вызовы обратного вызова onCachedBytesRead ().

Вы также можете открыть обозреватель своего устройства во время его работы на аппаратном устройстве, подключенном к Android Studio, и вы должны увидеть что-то в этом роде:

введите описание изображения здесь

Как видите, после первого просмотра видео оно было кэшировано в указанном мной месте, и после этого Exoplayer2 берет видео оттуда, а не из Интернета.

Для потокового видео убедитесь, что вы используете URL-адрес, например:

https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4.

а НЕ что-то уникальное, например, просмотр YouTube, т.е.

https://www.youtube.com/watch?v=Cn8PiqIXEjQ

Кроме того, в этом примере используется источник мультимедиа типа Exoplayer2 Progressive для потоковой передачи Uri из Интернета (который теперь является стандартом по умолчанию, так как тип Extractor был объявлен устаревшим некоторое время назад). Если URL-адрес, введенный в это приложение, указывает на файл, требующий типа DASH, очевидно, что это не сработает.

В Исходном коде Exoplayer2 есть пример приложения, которое выполняет гораздо более сложную работу по тестированию различных реализаций источников мультимедиа. против целевого Ури, пока он не найдет правильного onw.

здесь есть хорошее базовое руководство по Exoplayer2.

====

Оказывается, что это не кеширование является поведением по умолчанию, тогда в обычном процессе управления DataSource оно в большинстве случаев включено. В моем случае это было не так, потому что рассматриваемый Uri был content: // Uri, указывающим на Dropbox, который разрешается настраиваемым поставщиком SAF, который использует API Dropbox для потоковой передачи видео обратно. Таким образом, длина файла никогда не распознается, поэтому поведение по умолчанию не изменяется.

Оливер Вудман из команды Exoplayer предложил два решения. Первый (самый быстрый) заключался в изменении моей пользовательской реализации DataSourceFactory с возврата CacheDataSource на возврат настраиваемого DataSource, который обертывает CacheDataSource - перенаправление вызовов методов из интерфейса DataSource в экземпляр CacheDataSource, за исключением метод open (). В этом случае он сначала изменяет аргумент DataSpec, а затем передает его:

/**
 * Class specifically to turn off the flag that would not cache streamed docs.
 */
private class InTouchCacheDataSource implements DataSource {
    private CacheDataSource cacheDataSource;
    InTouchCacheDataSource(CacheDataSource cacheDataSource) {
        this.cacheDataSource = cacheDataSource;
    }

    @Override
    public void addTransferListener(TransferListener transferListener) {
        cacheDataSource.addTransferListener(transferListener);
    }

    @Override
    public long open(DataSpec dataSpec) throws IOException {
        return cacheDataSource.open(dataSpec
                .buildUpon()
                .setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN)
                .build());
    }

    @Nullable
    @Override
    public Uri getUri() {
        return cacheDataSource.getUri();
    }

    @Override
    public void close() throws IOException {
        cacheDataSource.close();
    }

    @Override
    public int read(byte[] target, int offset, int length) throws IOException {
        return cacheDataSource.read(target, offset, length);
    }
}

Затем этот класс используется настраиваемой фабрикой следующим образом:

@Override
public DataSource createDataSource() {
    CacheDataSource dataSource = new CacheDataSource(InTouchUtils.getMediaCache(),
            defaultDatasourceFactory.createDataSource(),
            new FileDataSource(),
            new CacheDataSink(InTouchUtils.getMediaCache(), maxCacheFragmentSize),
            CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
            listener);
    return new InTouchCacheDataSource(dataSource);
}

Это работает отлично, и Оливер уверяет меня, что Exoplayer поступит правильно, даже если поток заполнит кеш (т.е. сначала вызовет выселитель, а затем, если это не удастся, прекратите попытки записи в кеш и просто продолжите поток).

Другой вариант - написать собственный источник данных с использованием ContentDataSource в качестве начала и включить в него настроенный SAF (поскольку, по крайней мере, в моем случае я действительно знаю размер файла, но стандартные методы SAF используются для предоставления возможности чтобы передать это обратно в стандартный подход поставщика / распознавателя SAF).

person tfrysinger    schedule 26.01.2021
comment
Не могли бы вы сделать бесплатный средний пост из этого, пожалуйста - person Pemba Tamang; 23.04.2021
comment
Я пытаюсь сделать то же самое. Я даже скопировал ваш код, который играет песня, но ничего не кэшируется, и даже если это так, я все еще не знаю, как сохранить файл. - person Pemba Tamang; 23.04.2021
comment
Вы также реализуете собственный DataSourceFactory? Приведенное выше решение предполагает этот случай, а затем в этой фабрике вы возвращаете экземпляр чего-то вроде того, что я написал выше, то есть обернутого CacheDataSource. Затем вы используете этот настраиваемый DataSourceFactory в своем коде, например: - person tfrysinger; 24.04.2021
comment
частный статический ProgressiveMediaSource.Factory getCachedMediaSourceFactory (контекст контекста, прослушиватель CacheDataSource.EventListener) {if (cachedMediaSourceFactory == null) {cachedMediaSourceFactory = new ProgressiveMediaSource.Factory (new InTouchCacheFactory_SearchCacheMediaSource) } return cachedMediaSourceFactory; } - person tfrysinger; 24.04.2021
comment
onCachedBytesRead не запускается при первом запуске приложения. Почему это могло быть? - person Pemba Tamang; 26.04.2021
comment
Я хочу сохранить кеш как файл после завершения кеширования - person Pemba Tamang; 26.04.2021
comment
Ну, это не будет вызвано, пока вы не заработаете свой кеш. Что касается сохранения кеша в виде файла, я не думаю, что это возможно - система кеширования является внутренней для Exoplayer и не похожа на файл. Это целый набор частей, которые собираются вместе по мере необходимости для воспроизведения видео. - person tfrysinger; 27.04.2021
comment
Я пытался это сделать, и мне удалось сохранить кеш как файл, но я могу либо сохранить кеш, либо воспроизвести поток, но не оба сразу. Не могли бы вы взглянуть на это. Все, что я пытаюсь сделать, это найти способ узнать, завершено ли кеширование, и каким-то образом идентифицировать кеш, чтобы правильно его сохранить. - person Pemba Tamang; 28.04.2021
comment
stackoverflow.com/questions/67286053/ - person Pemba Tamang; 28.04.2021
comment
А вот и мое репо, где я сохраняю и кеширую одну песню. Я играю песню в первый раз и сохраняю ее во второй раз. По-прежнему нет возможности идентифицировать кеш. github.com/PembaTamang/Poc - person Pemba Tamang; 28.04.2021
comment
Привет, я скачал твое репо. Я не разработчик Kotlin, поэтому некоторые вещи здесь для меня немного чужды, но вы делаете некоторые вещи немного иначе, чем я - например, я не использую диспетчер загрузок, а мой собственный DataSourceFactory заключен в ProgressiveMediaSource .Фабрика. Я посмотрю, смогу ли я разработать полный рабочий пример и опубликовать его. Скорее всего, будет немного, потому что я сейчас завален, но постараюсь добраться до него как можно быстрее. - person tfrysinger; 28.04.2021
comment
Привет, менеджер загрузок неполный, и я им не пользуюсь. Было бы действительно здорово, если бы вы заставили его работать. Большое спасибо за ваше время и усилия. - person Pemba Tamang; 29.04.2021
comment
Пемба - я создал образец приложения - на Java, а не на Kotlin, извините! ;) - показывает, как создать кешированный источник данных для Exoplayer2. Фактически он показывает разницу между воспроизведением локального файла (кеш не требуется) и удаленного файла (используется кеш). Откройте logcat после первого воспроизведения видео и воспроизведите его снова, чтобы увидеть, как попадает в кеш. github.com/tfrysinger/ExoPlayerCacheDataSourceSample - person tfrysinger; 03.05.2021
comment
Я также отредактировал свой ответ выше, добавив некоторые подробности - см. Вверху. - person tfrysinger; 03.05.2021
comment
Большое спасибо за ваше время и усилия. Трудно найти человека, который готов помочь так же, как вы. Привет, котлин - это то же самое, что и java. Я имею в виду, если вы знаете java, вы знаете, что котлин - это то, что я чувствую. Некоторое время назад я только начал проект и выбрал kotlin вместо java - вот как я начал. - person Pemba Tamang; 03.05.2021
comment
Привет, @trysinger, я застрял в одном. После того, как я вызываю clearMediaItems () в exoplayer, я добавляю источник мультимедиа и вызываю prepare (), но он не запускает буфер. Что мне не хватает? - person Pemba Tamang; 03.05.2021
comment
Трудно устранить неполадки, не видя кода. Вы обновили свой проект Github? - person tfrysinger; 03.05.2021
comment
Я только что сделал. Вы можете посмотреть строку 78 здесь. github.com/PembaTamang/Poc/blob/main/ExoPlayerPoc2/app/src/main/ - person Pemba Tamang; 03.05.2021
comment
Я обнаружил, что мне нужно вызвать stop () перед вызовом clearMediaItems () для сброса exoplayer. Сейчас он работает в этом репо, но не в моем основном проекте. Хотел бы я добавить вас туда. Кажется, я что-то упускаю. В любом случае спасибо за ответ, чувак !!!!! - person Pemba Tamang; 03.05.2021
comment
Привет, чувак, мне стыдно просить так многого от тебя, но не могли бы вы взглянуть на это, пожалуйста, stackoverflow.com/questions/67369657/ - person Pemba Tamang; 03.05.2021