Записать аудиопоток в файл на Android 10
Есть приложение, которое воспроизводит аудиопоток из интернета и при нажатии на кнопку запись начинает записывать аудио в файл mp3. На Android 9 и ниже это работает, но из-за изменений доступа к хранилищу в Android 10 перестало работать (не создается папка и сам файл соответственно не записывается). Как переделать код под Android 10?
Класс Recorder
public class Recorder extends AsyncTask {
private Context context;
private String urlPath;
private String recordedFileName;
private boolean isRecording = false;
public Recorder() {}
public Recorder(Context context, String url, String recordedFilePath) {
this.context = context;
this.urlPath = url;
this.recordedFileName = recordedFilePath;
}
@Override
protected Object doInBackground(Object[] objects) {
isRecording = true;
try {
URL url = new URL(urlPath);
InputStream inputStream = url.openStream();
File direct = new File(Environment.getExternalStorageDirectory() + File.separator + AppConstants.dirName);
if (!direct.exists()) {
File musicFolder = new File(Environment.getExternalStorageDirectory() + File.separator + AppConstants.dirName);
musicFolder.mkdirs();
}
File file = new File(Environment.getExternalStorageDirectory() + File.separator + AppConstants.dirName, recordedFileName);
System.out.println(file.getAbsoluteFile());
OutputStream outputStream = new FileOutputStream(file);
byte[] buffer = new byte[4*1024];
int read;
while ((read = inputStream.read(buffer)) != -1) {
if(isCancelled())
break;
outputStream.write(buffer,0,read);
}
outputStream.flush();
outputStream.close();
inputStream.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void record()
{
File file = new File(Environment.getExternalStorageDirectory() + File.separator + AppConstants.dirName,recordedFileName);
if (file.exists()) {
file.delete();
}
this.execute();
}
public void stopRecording() {
isRecording=false;
this.cancel(true);
}
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
public String getUrlPath() {
return urlPath;
}
public void setUrlPath(String urlPath) {
this.urlPath = urlPath;
}
public String getRecordedFileName() {
return recordedFileName;
}
public void setRecordedFileName(String recordedFileName) {
this.recordedFileName = recordedFileName;
}
public boolean isRecording() {
return isRecording;
}
public void setRecording(boolean recording) {
this.isRecording = recording;
}
}
AudioActivity
public class PlayerActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
private String audioUrl;
private String audioImg;
private String audioTitle;
private int audioID;
public static boolean isPlay = false;
private Recorder recorder; //Для записи аудиопотока
private String recordedFileName = null; //Для записи аудиопотока
private boolean isRecording = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ButterKnife.bind(this); //Для записи аудиопотока
***
}
@OnClick(R.id.btnStartRecord)
public void onClickPlay() {
if (!isPlay) {
Toast.makeText(this, "Запустите аудио", Toast.LENGTH_SHORT).show();
} else {
int permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permissionStatus == PackageManager.PERMISSION_GRANTED) {
reCreateRecorder();
Toast.makeText(this, "Запись audio: " + audioTitle, Toast.LENGTH_SHORT).show();
recorder.record();
isRecording = true;
btnStartRecord.setVisibility(View.GONE);
layoutTime.setVisibility(View.VISIBLE);
btnStopRecord.setVisibility(View.VISIBLE);
mChronometer.setBase(SystemClock.elapsedRealtime());
mChronometer.start();
} else {
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
AppConstants.REQUEST_CODE_PERMISSION_WRITE_EXTERNAL_STORAGE);
layoutTime.setVisibility(View.GONE);
}
}
}
@OnClick(R.id.btnStopRecord)
public void onClickPause() {
btnStartRecord.setVisibility(View.VISIBLE);
layoutTime.setVisibility(View.GONE);
btnStopRecord.setVisibility(View.GONE);
recorder.stopRecording();
isRecording = false;
mChronometer.stop(); //Останавливаем таймер
Toast.makeText(this, "Файл сохранен в: " + Environment.getExternalStorageDirectory() +
File.separator + AppConstants.dirName, Toast.LENGTH_SHORT).show();
reCreateRecorder();
}
public void reCreateRecorder() {
if (recorder != null) {
if (recorder.isRecording()) recorder.stopRecording();
recorder = null;
}
recordedFileName = "mymusic-" + Calendar.getInstance().getTimeInMillis()/1000L + ".mp3";
recorder = new Recorder(this, audioUrl, recordedFileName);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case AppConstants.REQUEST_CODE_PERMISSION_WRITE_EXTERNAL_STORAGE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
onClickPlay();
} else {
Toast.makeText(this, "Нет доступа к хранилищу!", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
Ответы (2 шт):
Вам придется сильно переделать код.
1)Запускаем Intent.ACTION_CREATE_DOCUMENT - помещаете его в своем Activity/Fragment, привязав его например к меню или к какому-либо действию юзера (типа а давай-ка запишем поток в файл), примерно так:
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/mp3"); //не уверен, что у вас mp3
String filename = "mymusic.mp3";
intent.putExtra(Intent.EXTRA_TITLE, filename); //по сути подсказка юзеру какой файл надо создавать, но юзер может его изменить
startActivityForResult(intent, MY_REQUEST_CREATE_FILE);
2)В том же Activity/Fragment ловите onActivityResult() там пишете код примерно такой:
Uri uri = intent.getData(); //URI создаваемого файла выбранного юзером
DocumentFile documentFile = DocumentFile.fromSingleUri(this.getContext(), uri);
// открываем поток для записи
fileStream = this.getContext().getContentResolver().openOutputStream(uri);
3)Теперь у вас есть fileStream - поток куда надо записать ваш драгоценный аудиопоток. По желанию можете его оформить внутрь AsyncTask или чего-то подобного. По окончанию записи не забудьте закрыть поток.
Сделала немного иначе. Следующим образом:
public class Recorder extends AsyncTask {
@Override
protected Object doInBackground(Object[] objects) {
isRecording = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
URL url = new URL(urlPath);
InputStream inputStream = url.openStream();
ContentValues values = new ContentValues();
values.put(MediaStore.Audio.Media.DISPLAY_NAME, recordedFileName);
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/*");
values.put(MediaStore.Audio.Media.IS_PENDING, 1);
ContentResolver contentResolver = context.getContentResolver();
Uri collection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri item = contentResolver.insert(collection, values);
System.out.println(item.getLastPathSegment());
ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(item, "w", null);
OutputStream outputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
FileOutputStream fos = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
byte[] buffer = new byte[4*1024];
int read;
while ((read = inputStream.read(buffer)) != -1) {
if(isCancelled())
break;
outputStream.write(buffer,0,read);
}
outputStream.flush();
outputStream.close();
inputStream.close();
fos.close();
contentResolver.update(item, values, null, null);
values.clear();
values.put(MediaStore.Audio.Media.IS_PENDING, 0);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} else {
//Код для версий ниже Android 10
}
return null;
}
}
Аудио записывается. Правда не совсем понимаю нужно ли для Android 10 и выше как-то иначе получить разрешение для работы с хранилищем. Сейчас код описанный выше в вопросе для получения разрешения дает разрешение и этот код записывает потоковое аудио. Если в моем коде есть ошибки или неправильная логика подправьте пожалуйста).