I published an app recently, and users are reporting crashes because my program included the method setNextMediaPlayer(). I now realize that this only works for API 16+, and my app supports API 8+. I was wondering if there is an alternate way of achieving the same effect.
My app has some text to speech, and what I was doing was creating an ArrayList of MediaPlayers that each had a short sound file, and then playing them one after an other. If I remove this method from the code, the audio becomes too choppy to understand.
I was thinking about using the SoundPool class, but there is no OnCompleteListener, so I'm not sure how I would do it.
So basically my question is: Is there a way to transition seamlessly between audio files without using the setNextMediaPlayer() method?
Thanks very much for your time!
EDIT
I added this code that I found
private class CompatMediaPlayer extends MediaPlayer implements OnCompletionListener {
private boolean mCompatMode = true;
private MediaPlayer mNextPlayer;
private OnCompletionListener mCompletion;
public CompatMediaPlayer() {
try {
MediaPlayer.class.getMethod("setNextMediaPlayer", MediaPlayer.class);
mCompatMode = false;
} catch (NoSuchMethodException e) {
mCompatMode = true;
super.setOnCompletionListener(this);
}
}
public void setNextMediaPlayer(MediaPlayer next) {
if (mCompatMode) {
mNextPlayer = next;
} else {
super.setNextMediaPlayer(next);
}
}
#Override
public void setOnCompletionListener(OnCompletionListener listener) {
if (mCompatMode) {
mCompletion = listener;
} else {
super.setOnCompletionListener(listener);
}
}
#Override
public void onCompletion(MediaPlayer mp) {
if (mNextPlayer != null) {
// as it turns out, starting a new MediaPlayer on the completion
// of a previous player ends up slightly overlapping the two
// playbacks, so slightly delaying the start of the next player
// gives a better user experience
SystemClock.sleep(50);
mNextPlayer.start();
}
mCompletion.onCompletion(this);
}
}
But now how do I add the audio files? I tried this:
// assigns a file to each media player
mediaplayers = new ArrayList<CompatMediaPlayer>();
for (int i = 0; i < files.size(); i++) {
mediaplayers.add((CompatMediaPlayer) CompatMediaPlayer.create(this, files.get(i)));
}
but am getting a class cast exception because MediaPlayer cannot be casted to CompatMediaPlayer.
Create Compat player that will work with onCompletionListener to start the next player like:
public void onCompletion(MediaPlayer mp) {
if (mCompatMode && mNextPlayer != null) {
mNextPlayer.prepare();
mNextPlayer.start();
}
}
Somewhere in your constructor check if there is method (or check SDK version) named "setNextMediaPlayer"
mCompatMode = Build.VERSION.SDK_INT < 16;
Define method like this one:
public void setNextMediaPlayer(MediaPlayer next) {
if (mCompatMode) {
mNextPlayer = next;
} else {
super.setNextMediaPlayer(next);
}
}
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
mediaPlayer = new MediaPlayer();
try {
AssetFileDescriptor descriptor = context.getAssets().openFd("play1.mp3");
mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.start();
MediaPlayer md = new MediaPlayer();
try {
AssetFileDescriptor descriptor = context.getAssets().openFd("play2.mp3");
md.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
md.setAudioStreamType(AudioManager.STREAM_MUSIC);
md.prepare();
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.setNextMediaPlayer(md);
MediaPlayer md1 = new MediaPlayer();
try {
AssetFileDescriptor descriptor = context.getAssets().openFd("play3.mp3");
md1.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
descriptor.close();
md1.setAudioStreamType(AudioManager.STREAM_MUSIC);
md1.prepare();
} catch (IOException e) {
e.printStackTrace();
} md.setNextMediaPlayer(md1);
Related
I am building an application that requires an alarm, and if the user has muted their device or set it to DND, I want to know how to override it.
I am really struggling with clear details on how the permissions on the sound and notification system work, where they overlap etc. I would like to have the user enable the right to have their DND preferences overridden.
To be clear. Is there an call I can make to override the sound or notification settings set buy the user at the device level and if there is, what permissions if any do I need?
I am sorry if this is too general and understand if it is delisted.
Try:
private void playSound(Context context, int soundResId, int volFactor) {
if (context != null) {
try {
MediaPlayer mediaPlayer = new MediaPlayer();
AssetFileDescriptor assetFileDescriptor = context.getResources().openRawResourceFd(soundResId);
if (assetFileDescriptor != null) {
mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
assetFileDescriptor.close();
}
if (mediaPlayer != null) {
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
try {
mediaPlayer.reset();
mediaPlayer.release();
} catch (Exception e) {
e.printStackTrace();
} finally {
mp = null;
}
}
});
mediaPlayer.setOnErrorListener(new OnErrorListener() {
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
try {
mediaPlayer.reset();
mediaPlayer.release();
} catch (Exception e) {
e.printStackTrace();
} finally {
mp = null;
}
return true;
}
});
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_MUSIC)/volFactor, 0);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mediaPlayer.prepare();
mediaPlayer.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Also in manifest:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
im making a media player that plays mp3s mixes stored on firebase. i can make the links play no problem. but when i press the item again i want it to be the equivalent of pressing stop. but for some reason it does not stop it starts a new instance of the media. can some one tell me what ive done wrong please.
my code
in my on create
mMediaplayer = null;
then my method
private void fetchAudioUrlFromFirebase() throws IOException {
String mp3 = mp3url;
mMediaplayer = new MediaPlayer();
mMediaplayer.setDataSource(mp3);
mMediaplayer.prepare();//prepare to play
if (mMediaplayer.isPlaying()) {
stopPlaying();
} else {
playMedia();
}
}
private void stopPlaying() {
if (mMediaplayer != null) {
mMediaplayer.stop();
}
}
private void playMedia() {
mMediaplayer.start();
}
}
then in item onclick
try {
fetchAudioUrlFromFirebase();
} catch (IOException e) {
e.printStackTrace();
}
issue : lets assume media player is already playing so
// song is playing
mMediaplayer = new MediaPlayer(); // you created a new player
mMediaplayer.setDataSource(mp3);
mMediaplayer.prepare();//prepare to play
if (mMediaplayer.isPlaying()) { // new player is not in playing state
stopPlaying(); // so you always checking the state of new player
} else {
playMedia();
}
check first then create
if (mMediaplayer!=null && mMediaplayer.isPlaying()) {
stopPlaying();
}
mMediaplayer = new MediaPlayer();
mMediaplayer.setDataSource(mp3);
mMediaplayer.prepare();//prepare to play
playMedia();
so the logic can be reduced to
if (mMediaplayer!=null && mMediaplayer.isPlaying()) {
mMediaplayer.stop();
mMediaplayer.release();
}
mMediaplayer = new MediaPlayer();
mMediaplayer.setDataSource(mp3);
mMediaplayer.prepare();//prepare to play
mMediaplayer.start();
I'm trying to access mp3 files from the assets folder and then setting the data source, preparing them and then playing them when an imagebutton is clicked. I keep getting an error on getAssets(), though. I know I need some context but I am not sure how to do it in my case. Every way that I have tried gives me some sort of error.
public class SoundFile {
public final MediaPlayer mp;
ImageButton position;
public SoundFile(Activity activity, int soundfile, int imgButtonId) {
this.mp = new MediaPlayer();
this.position = (ImageButton)activity.findViewById(imgButtonId);
this.position.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try{
mp.reset();
AssetFileDescriptor afd;
afd = getAssets().openFd(mp);
mp.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getLength());
mp.prepare();
mp.start();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}});}}
Try This
Replace
afd = getAssets().openFd(mp);
with
afd =activity.getAssets().openFd(mp);
I am developing an application which can record calls in Android. I have read a lot of topics where the call recording problem was discussed. And i know that not all Android phones can record calls. But i am wondering how can record calls the most popular applications on Play Market, such as https://play.google.com/store/apps/details?id=com.appstar.callrecorder or https://play.google.com/store/apps/details?id=polis.app.callrecorder. I think that thy are using not on MediaRecorder class to do this job, but also something else. Because i have developed my own application, but i can record only my voice. But these two applications are recording both my voice and the voice of a man to whom i am calling. How they are doing this? I know that we can't get an access to device speaker to record sound from it. Could you give me some ideas of how to record voice calls? Here is my code that i am using in my application:
public class CallRecorderService extends Service {
private MediaRecorder mRecorder;
private boolean isRecording = false;
private PhoneStateListener phoneStateListener = new PhoneStateListener() {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
stopRecording();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
startRecording(incomingNumber);
break;
case TelephonyManager.CALL_STATE_RINGING:
break;
default:
break;
}
}
};
#Override
public void onCreate() {
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
super.onCreate();
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
private void startRecording(String number) {
try {
String savePath = Environment.getExternalStorageDirectory().getAbsolutePath();
savePath += "/Recorded";
File file = new File(savePath);
if (!file.exists()) {
file.mkdir();
}
savePath += "/record_" + System.currentTimeMillis() + ".amr";
mRecorder = new MediaRecorder();
SharedPreferences sPrefs = getSharedPreferences(Constants.PREFERENCES_NAME, Context.MODE_PRIVATE);
int inputSource = sPrefs.getInt(Constants.SOURCE_INPUT, Constants.SOURCE_VOICE_CALL);
int outputSource = sPrefs.getInt(Constants.SOURCE_OUTPUT, Constants.OUTPUT_MPEG4);
switch (inputSource) {
case Constants.SOURCE_MIC:
increaseSpeakerVolume();
break;
}
mRecorder.setAudioSource(inputSource);
mRecorder.setOutputFormat(outputSource);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setOutputFile(savePath);
mRecorder.prepare();
mRecorder.start();
isRecording = true;
} catch (Exception ex) {
ex.printStackTrace();
}
}
private void stopRecording(){
if (isRecording) {
isRecording = false;
mRecorder.stop();
mRecorder.release();
}
}
private void increaseSpeakerVolume(){
AudioManager audio = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audio.adjustStreamVolume(AudioManager.STREAM_VOICE_CALL,
AudioManager.ADJUST_RAISE,
AudioManager.FLAG_SHOW_UI);
}
}
I think they do use MediaRecorder. Perhaps main problem with it is that, it is tricky to write the data from it into something else but file on local store. In order to work it around, you could create a pipe. Then use its file descriptor as an input fd for MediaRecorder. And the pipe output then could be controlled by some thread that will read the audio packages (in one of format, aac or.. wav even) and then do whatever you want with the data.
Here is some code. Note, this is a proto, may not even compile, just to give you an idea.
/* 2M buffer should be enough. */
private final static int SOCKET_BUF_SIZE = 2 * 1024 * 1000;
private void openSockets() throws IOException {
receiver = new LocalSocket();
receiver.connect(new LocalSocketAddress("com.companyname.media-" + socketId));
receiver.setReceiveBufferSize(SOCKET_BUF_SIZE);
sender = lss.accept();
sender.setSendBufferSize(SOCKET_BUF_SIZE);
}
private void closeSockets() {
try {
if (sender != null) {
sender.close();
sender = null;
}
if (receiver != null) {
receiver.close();
receiver = null;
}
} catch (Exception ignore) {
}
}
public void prepare() throws IllegalStateException, IOException {
int samplingRate;
openSockets();
if (mode == MODE_TESTING) {
samplingRate = requestedSamplingRate;
} else {
samplingRate = actualSamplingRate;
}
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
try {
Field name = MediaRecorder.OutputFormat.class.getField("AAC_ADTS");
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "AAC ADTS seems to be supported: AAC_ADTS=" + name.getInt(null));
mediaRecorder.setOutputFormat(name.getInt(null));
} catch (Exception e) {
throw new IOException("AAC is not supported.");
}
try {
mediaRecorder.setMaxDuration(-1);
mediaRecorder.setMaxFileSize(Integer.MAX_VALUE);
} catch (RuntimeException e) {
Log.e(StreamingApp.TAG, "setMaxDuration or setMaxFileSize failed!");
}
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setAudioChannels(1);
mediaRecorder.setAudioSamplingRate(samplingRate);
mediaRecorder.setOutputFile(sender.getFileDescriptor());
startListenThread(receiver.getInputStream());
mediaRecorder.start();
}
I generate midi files on the go. I want to play these files continuously.
I initialise a mediaplayer and start song1.mid. Then I use the following code to play song2.mid
// set on completion listener music file
mediaPlayer
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
String filePath2 = null;
File file = null;
FileInputStream inputStream = null;
//set the filePath
try {
filePath2 = getCacheDir() + "/optimuse" + song + ".mid";
file = new File(filePath2);
if (file.exists()) {
inputStream = new FileInputStream(file);
if (inputStream.getFD().valid()) {
System.out.println("Valid!");
}
}
} catch (Exception e1) {
e1.printStackTrace();
System.exit(-1);
}
//set Mediaplayer's datasource
if (file.exists()) {
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(inputStream.getFD());
inputStream.close();
} catch (Exception e1) {
e1.printStackTrace();
System.exit(-1);
}
try {
mediaPlayer.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//if the player is not running
if (!mediaPlayer.isPlaying()) {
//start the player
mediaPlayer.start();
Toast.makeText(MainActivity.this,
"mediaPlayer.start()", Toast.LENGTH_LONG)
.show();
}
}
});
}
});
The problem is that the mediaplayer stops after song2. But I want song3 to start. I can increment the global variable, ok. But the onCompletionListener does not seem to work when the second song finishes.
I guess I should initialise MediaPlayer mp to have an onCompletionListener too? Not sure what the best approach is.
Or should I do something like:
new class MediaPlayer implements OnCompletionListener(){
#Override
song++;
//code to start the mediaplayer
}
Thank you for putting me in the right direction. I am also a little bit concerned with efficiency, when I keep starting up new mediaplayers...
Basically, what I want to do is play song1.mid, song2.mid,... continuously, but the files are generated during the program.
EDIT
Thanks to the wonderful help of #Gan. I know have the following working code:
// set on completion listener music file
mediaPlayer
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
String filePath2 = null;
File file = null;
FileInputStream inputStream = null;
//set the filePath
try {
filePath2 = getCacheDir() + "/optimuse" + song + ".mid";
file = new File(filePath2);
if (file.exists()) {
inputStream = new FileInputStream(file);
if (inputStream.getFD().valid()) {
System.out.println("Valid!");
}
}
} catch (Exception e1) {
e1.printStackTrace();
System.exit(-1);
}
//set Mediaplayer's datasource
if (file.exists()) {
try {
mp.stop();
mp.reset();
mp.setDataSource(inputStream.getFD());
inputStream.close();
} catch (Exception e1) {
e1.printStackTrace();
System.exit(-1);
}
try {
mp.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//if the player is not running
if (!mp.isPlaying()) {
//start the player
mp.start();
Toast.makeText(MainActivity.this,
"mp.start()", Toast.LENGTH_LONG)
.show();
}
}
});
}
});
store the source locations in an array and in the onCompletionListener cycle through the source. something like this (use the same media player instance)
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp)
{
if(currentPosition<sourceArray.size())
{
mediaPlayer.reset();
/* load the new source */
mediaPlayer.setDataSource(sourceArray[position]);
/* Prepare the mediaplayer */
mediaPlayer.prepare();
/* start */
mediaPlayer.start();
}
else
{
/* release mediaplayer */
mediaPlayer.release();
}
}