Как играть в 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();