Как играть в InputStream аудиофайла, который не входит в URL-адрес или хранилище?

Фон

Мне удалось загрузить аудиофайл (3gp) в Google-Drive.

Теперь я хочу, чтобы иметь возможность воспроизводить файл в приложении.

API Google Диска позволяет только получить входной поток сохраненного там файла.

Проблема

Все возможности ввода-вывода MediaPlayer недоступны в моем случае, это только InputSteam:

http://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.io.FileDescriptor)

Я знаю, что я могу сохранить файл с Google-диска в кеш и воспроизвести его, но я хочу избежать обработки хранилища и играть в файл на лету.

Что я пробовал

Я попытался найти эту проблему и обнаружил, что возможно использование AudioTrack ( здесь). Возможно также использование новых функций Jelly- Bean (показано здесь, найденное из здесь), но я не уверен, что это довольно низкий уровень.

К сожалению, используя AudioTrack, я получил неправильные звуки (шум).

Я также заметил, что MediaPlayer имеет возможность установить источник данных в MediaDataSource ( здесь), но не только я не уверен, как его использовать, он также требует API 23 и выше.

Конечно, я пробовал использовать URL-адрес, который указан в Google-Drive, но это используется только для других целей и не направляется на аудиофайл, поэтому его нельзя использовать с помощью MediaPlayer.

Вопрос

Учитывая InputStream, можно ли использовать AudioTrack или что-то еще, чтобы воспроизвести аудиофайл 3gp?

Возможно, есть решение для библиотеки поддержки?

Ответ 1

Если ваш minSdkVersion равен 23 или выше, вы можете использовать setDataSource(MediaDataSource) и предоставить свой собственный подкласс abstract MediaDataSource class.

Для более старых устройств вы можете использовать канал, созданный с помощью ParcelFileDescriptor. У вас будет поток, который записывает данные в конец вашего канала и передает FileDescriptor (от getFileDescriptor()) для конца проигрывателя до setDataSource(FileDescriptor).

Ответ 2

Самый простой пример реализации MediaDataSource:

import android.media.MediaDataSource;
import android.os.Build;
import android.support.annotation.RequiresApi;

import java.io.IOException;
import java.io.InputStream;

@RequiresApi(api = Build.VERSION_CODES.M)
public class InputStreamMediaDataSource extends MediaDataSource {
    private InputStream is;
    private long streamLength = -1, lastReadEndPosition;

    public InputStreamMediaDataSource(InputStream is, long streamLength) {
        this.is = is;
        this.streamLength = streamLength;
        if (streamLength <= 0){
            try {
                this.streamLength = is.available(); //Correct value of InputStream#available() method not always supported by InputStream implementation!
            } catch (IOException e) {
                e.printStackTrace();
            }
        }                 
    }

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

    @Override
    public synchronized int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
        if (position >= streamLength)
            return -1;

        if (position + size > streamLength)
            size -= (position + size) - streamLength;

        if (position < lastReadEndPosition) {
            is.close();
            lastReadEndPosition = 0;
            is = getNewCopyOfInputStreamSomeHow();//new FileInputStream(mediaFile) for example.
        }

        long skipped = is.skip(position - lastReadEndPosition);
        if (skipped == position - lastReadEndPosition) {
            int bytesRead = is.read(buffer, offset, size);
            lastReadEndPosition = position + bytesRead;
            return bytesRead;
        } else {
            return -1;
        }
    }

    @Override
    public synchronized long getSize() throws IOException {
        return streamLength;
    }
}

Чтобы использовать его с API >= 23, вы должны указать значение streamLength, и если (когда) MediaPlayer вернется - т.е. position < lastReadEndPosition, вы должны знать, как создать новую копию InputStream.

Пример использования:

Вам нужно создать Activity, инициализировать класс MediaPlayer (есть много примеров воспроизведения файла) и разместить следующий код istead старого player.setDataSource("/path/to/media/file.3gp")

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    File file = new File("/path/to/media/file.3gp");//It is just an example! If you have real file on the phone memory, you don't need to wrap it to the InputStream to play it in MediaPlayer!
    player.setDataSource(new InputStreamMediaDataSource(new FileInputStream(file), file.length()));
} else
    player.setDataSource(this, mediaUri);

Если ваш файл является объектом com.google.api.services.drive.model.File на Google Диске и com.google.api.services.drive.Drive drive, вы можете получить

InputStream is = drive.getRequestFactory().buildGetRequest(new GenericUrl(file.getDownloadUrl())).execute().getContent();

В случае Build.VERSION.SDK_INT < Build.VERSION_CODES.M мне пришлось установить HTTP-сервер на локальном хосте Android-устройства (с помощью NanoHTTPd) и передайте поток байтов через этот сервер в MediaPlayer по uri - player.setDataSource(this, mediaUri)

Ответ 3

Для всех, кто заинтересован в использовании реализации MediaDataSource, я создал тот, который читает и кэширует буферы данных. Он работает из любого InputStream, я в основном создал его для чтения из сетевых файлов с помощью JCIFS SmbFile.

Вы можете найти его на https://github.com/SteveGreatApe/BufferedMediaDataSource

Ответ 4

Если вы спрашиваете о медиаплеерах, которые устанавливают наш путь аудио, возможно, этот код может помочь.

File directory = Environment.getExternalStorageDirectory();
		File file = new File( directory + "/AudioRecorder" );
		String AudioSavePathInDevice = file.getAbsolutePath() + "/" + "sample.wav" ;
    
     mediaPlayer = new MediaPlayer();
     
      try {
            mediaPlayer.setDataSource(AudioSavePathInDevice);
            mediaPlayer.prepare();
          } catch (IOException e) {
              e.printStackTrace();
          }

      mediaPlayer.start();