I'm developing an mobile App for my household. I want to give my family the possibilty, to send their smartphone sounds to several bluetooth speakers in the house. I thought, to send the sound from the phone via the wifi to a server in the house, which connects to the speakers and play the sound.
The connection to the server is already working, but when I accept the Streaming/Recording Dialog, the App Crashes with this error:
2023-10-02 16:12:35.160 9346-9346/de.gamingwave.homeapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: de.gamingwave.homeapp, PID: 9346
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { (has extras) }} to activity {de.gamingwave.homeapp/de.gamingwave.homeapp.musik.MusikActivity}: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
at android.app.ActivityThread.deliverResults(ActivityThread.java:4845)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
at android.os.Parcel.createException(Parcel.java:2071)
at android.os.Parcel.readException(Parcel.java:2039)
at android.os.Parcel.readException(Parcel.java:1987)
at android.media.projection.IMediaProjection$Stub$Proxy.start(IMediaProjection.java:231)
at android.media.projection.MediaProjection.<init>(MediaProjection.java:58)
at android.media.projection.MediaProjectionManager.getMediaProjection(MediaProjectionManager.java:104)
at de.gamingwave.homeapp.musik.MusikActivity.onActivityResult(MusikActivity.java:99)
at android.app.Activity.dispatchActivityResult(Activity.java:8110)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4838)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4886)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.media.projection.MediaProjectionManagerService$MediaProjection.start(MediaProjectionManagerService.java:476)
at android.media.projection.IMediaProjection$Stub.onTransact(IMediaProjection.java:135)
at android.os.Binder.execTransactInternal(Binder.java:1021)
at android.os.Binder.execTransact(Binder.java:994)
Here is my code:
Do you know what's wrong?
package de.gamingwave.homeapp.musik;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.*;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import de.gamingwave.homeapp.R;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class MusikActivity extends AppCompatActivity {
private static final int SAMPLE_RATE = 44100;
private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private static final int BUFFER_SIZE = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
private AudioRecord audioRecord;
private boolean isStreaming = false;
private Socket socket;
private OutputStream outputStream;
private Button startButton;
private Button stopButton;
private static final int RECORD_AUDIO_PERMISSION_CODE = 101;
public boolean rec = false;
private MediaProjectionManager mediaProjectionManager;
private MediaProjection mediaProjection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_musik);
Button rec_btn = findViewById(R.id.recording);
rec_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isStreaming) {
new AudioStreamingTask().execute();
rec_btn.setText("Streaming stoppen");
rec_btn.setBackgroundColor(Color.RED);
} else {
stopStreaming();
rec_btn.setText("Streaming starten");
rec_btn.setBackgroundColor(Color.GREEN);
}
}
});
// Überprüfen Sie die Berechtigung zur Aufnahme von Audio
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, RECORD_AUDIO_PERMISSION_CODE);
}
mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
// Starten Sie die Benutzeranforderung für die MediaProjection
Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(permissionIntent, 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Benutzer hat die Berechtigung erteilt
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
// Initialisieren Sie audioRecord hier
audioRecord = createAudioRecord();
// Starten Sie den MediaProjectionForegroundService und übergeben Sie die Media-Projektion
startMediaProjection();
}
private void startMediaProjection() {
// Erstellen Sie einen Intent für den Vordergrunddienst
Intent serviceIntent = new Intent(this, MediaProjectionForegroundService.class);
serviceIntent.setAction("start_media_projection");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
}
private void stopMediaProjection() {
// Erstellen Sie einen Intent für den Vordergrunddienst, um die Media-Projektion zu stoppen
Intent serviceIntent = new Intent(this, MediaProjectionForegroundService.class);
serviceIntent.setAction("stop_media_projection");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
}
private AudioRecord createAudioRecord() {
AudioRecord audio = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
AudioPlaybackCaptureConfiguration config;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
config = new AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
.addMatchingUsage(AudioAttributes.USAGE_MEDIA)
.build();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
return null;
}
audio = new AudioRecord.Builder()
.setAudioPlaybackCaptureConfig(config)
.build();
} else {
Toast.makeText(this, "Error", Toast.LENGTH_LONG).show();
}
}
return audio;
}
private void startStreaming() {
if (!isStreaming) {
try {
socket = new Socket("IP-Address", 12345);
outputStream = socket.getOutputStream();
if (audioRecord != null) {
audioRecord.startRecording();
isStreaming = true;
// Starten Sie einen Hintergrundthread für die Echtzeitübertragung
new Thread(new Runnable() {
@Override
public void run() {
sendAudioData();
}
}).start();
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MusikActivity.this, "Audioaufnahme konnte nicht initialisiert werden.", Toast.LENGTH_SHORT).show();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void sendAudioData() {
try {
byte[] buffer = new byte[BUFFER_SIZE];
while (isStreaming) {
int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE);
if (bytesRead != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void stopStreaming() {
if (isStreaming) {
isStreaming = false;
try {
if (audioRecord != null) {
audioRecord.stop();
audioRecord.release();
}
if (outputStream != null) {
outputStream.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == RECORD_AUDIO_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Berechtigung erteilt
} else {
Toast.makeText(this, "Aufnahmeberechtigung wurde verweigert.", Toast.LENGTH_SHORT).show();
}
}
}
private class AudioStreamingTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
startStreaming();
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// Nach der Ausführung können Sie ggf. UI-Aktualisierungen hier vornehmen
}
}
}