Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random ringtone playing #126

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
wait for all ringtones to finish when selecting random alarms
Nilsu11 committed Dec 29, 2024
commit 9119acf2f13a249888fe90761a0bd437b161ec43
242 changes: 1 addition & 241 deletions app/src/main/java/com/best/deskclock/AsyncRingtonePlayer.java
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@
import androidx.annotation.NonNull;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -269,7 +268,7 @@ private interface PlaybackDelegate {
}

/**
* Loops playback of a ringtone using {@link MediaPlayer}.
* Loops playback of a or multiple ringtones using {@link MediaPlayer}.
*/
private class MediaPlayerPlaybackDelegate implements PlaybackDelegate {

@@ -484,243 +483,4 @@ public boolean adjustVolume() {
}
}

/**
* Loops playback of a ringtone using {@link Ringtone}.
*/
private class RingtonePlaybackDelegate implements PlaybackDelegate {

/**
* The audio focus manager. Only used by the ringtone thread.
*/
private AudioManager mAudioManager;

/**
* The current ringtone. Only used by the ringtone thread.
*/
private Ringtone mRingtone;

/**
* The method to adjust playback volume; cannot be null.
*/
private Method mSetVolumeMethod;

/**
* The method to adjust playback looping; cannot be null.
*/
private Method mSetLoopingMethod;

/** The ringtone playback attempts counter. */
private int mRingtonePlayRetries = 0;

/**
* The duration over which to increase the volume.
*/
private long mCrescendoDuration = 0;

/**
* The time at which the crescendo shall cease; 0 if no crescendo is present.
*/
private long mCrescendoStopTime = 0;

private RingtonePlaybackDelegate() {
try {
mSetVolumeMethod = Ringtone.class.getDeclaredMethod("setVolume", float.class);
} catch (NoSuchMethodException nsme) {
LOGGER.e("Unable to locate method: Ringtone.setVolume(float).", nsme);
}

try {
mSetLoopingMethod = Ringtone.class.getDeclaredMethod("setLooping", boolean.class);
} catch (NoSuchMethodException nsme) {
LOGGER.e("Unable to locate method: Ringtone.setLooping(boolean).", nsme);
}
}

/**
* Starts the actual playback of the ringtone. Executes on ringtone-thread.
*/
@Override
public boolean play(Context context, Uri ringtoneUri, long crescendoDuration) {
checkAsyncRingtonePlayerThread();
mCrescendoDuration = crescendoDuration;

LOGGER.i("Play ringtone via android.media.Ringtone.");

if (mAudioManager == null) {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}

final boolean inTelephoneCall = isInTelephoneCall(mAudioManager);
if (inTelephoneCall) {
ringtoneUri = getInCallRingtoneUri(context);
}

// Attempt to fetch the specified ringtone.
mRingtone = RingtoneManager.getRingtone(context, ringtoneUri);

if (mRingtone == null) {
// Fall back to the system default ringtone.
ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
mRingtone = RingtoneManager.getRingtone(context, ringtoneUri);
}

// Attempt to enable looping the ringtone.
try {
mSetLoopingMethod.invoke(mRingtone, true);
} catch (Exception e) {
LOGGER.e("Unable to turn looping on for android.media.Ringtone", e);

// Fall back to the default ringtone if looping could not be enabled.
// (Default alarm ringtone most likely has looping tags set within the .ogg file)
mRingtone = null;
}

// If no ringtone exists at this point there isn't much recourse.
if (mRingtone == null) {
LOGGER.i("Unable to locate alarm ringtone, using internal fallback ringtone.");
ringtoneUri = getFallbackRingtoneUri(context);
mRingtone = RingtoneManager.getRingtone(context, ringtoneUri);
}

try {
return startPlayback(inTelephoneCall);
} catch (Throwable t) {
LOGGER.e("Using the fallback ringtone, could not play " + ringtoneUri, t);
// Recover from any/all playback errors by attempting to play the fallback tone.
mRingtone = RingtoneManager.getRingtone(context, getFallbackRingtoneUri(context));
try {
return startPlayback(inTelephoneCall);
} catch (Throwable t2) {
// At this point we just don't play anything.
LOGGER.e("Failed to play fallback ringtone", t2);
}
}

return false;
}

/**
* Prepare the Ringtone for playback, then start the playback.
*
* @param inTelephoneCall {@code true} if there is currently an active telephone call
* @return {@code true} if a crescendo has started and future volume adjustments are
* required to advance the crescendo effect
*/
private boolean startPlayback(boolean inTelephoneCall) {
// Indicate the ringtone should be played via the alarm stream.
mRingtone.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build());

// Attempt to adjust the ringtone volume if the user is in a telephone call.
boolean scheduleVolumeAdjustment = false;
if (inTelephoneCall) {
LOGGER.v("Using the in-call alarm");
setRingtoneVolume(IN_CALL_VOLUME);
} else if (mCrescendoDuration > 0) {
setRingtoneVolume(0);

// Compute the time at which the crescendo will stop.
mCrescendoStopTime = Utils.now() + mCrescendoDuration;
scheduleVolumeAdjustment = true;
}

mAudioManager.requestAudioFocus(null, STREAM_ALARM, AUDIOFOCUS_GAIN_TRANSIENT);

mRingtone.play();

return scheduleVolumeAdjustment;
}

/**
* Sets the volume of the ringtone.
*
* @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0
* corresponds to no attenuation being applied.
*/
private void setRingtoneVolume(float volume) {
try {
mSetVolumeMethod.invoke(mRingtone, volume);
} catch (Exception e) {
LOGGER.e("Unable to set volume for android.media.Ringtone", e);
}
}

/**
* Stops the playback of the ringtone. Executes on the ringtone-thread.
*/
@Override
public void stop() {
checkAsyncRingtonePlayerThread();

LOGGER.i("Stop ringtone via android.media.Ringtone.");

mCrescendoDuration = 0;
mCrescendoStopTime = 0;

if (mRingtone != null && mRingtone.isPlaying()) {
LOGGER.d("Ringtone.stop() invoked.");
mRingtone.stop();
}

mRingtone = null;

if (mAudioManager != null) {
mAudioManager.abandonAudioFocus(null);
}
}

/**
* Adjusts the volume of the ringtone being played to create a crescendo effect.
*/
@Override
public boolean adjustVolume() {
checkAsyncRingtonePlayerThread();

// If ringtone is absent, ignore volume adjustment.
if (mRingtone == null) {
mCrescendoDuration = 0;
mCrescendoStopTime = 0;
return false;
}

// If ringtone is not playing, to avoid being muted forever recheck
// to ensure reliability.
if (!mRingtone.isPlaying()) {
if (mRingtonePlayRetries < 10) {
mRingtonePlayRetries++;
// Reuse the crescendo messaging looper to return here
// again.
return true;
}

mRingtonePlayRetries = 0;
mCrescendoDuration = 0;
mCrescendoStopTime = 0;
return false;
}

if (mRingtonePlayRetries > 0) {
mRingtonePlayRetries = 0;
// Compute again the time at which the crescendo will stop.
mCrescendoStopTime = Utils.now() + mCrescendoDuration;
}

// If the crescendo is complete set the volume to the maximum; we're done.
final long currentTime = Utils.now();
if (currentTime > mCrescendoStopTime) {
mCrescendoDuration = 0;
mCrescendoStopTime = 0;
setRingtoneVolume(1);
return false;
}

final float volume = computeVolume(currentTime, mCrescendoStopTime, mCrescendoDuration);
setRingtoneVolume(volume);

// Schedule the next volume bump in the crescendo.
return true;
}
}
}
7 changes: 5 additions & 2 deletions app/src/main/java/com/best/deskclock/Utils.java
Original file line number Diff line number Diff line change
@@ -965,8 +965,11 @@ public static int getRingtoneDuration(Context context, Uri ringtoneUri) {
final Uri uri = ringtoneManager.getRingtoneUri(i);
try {
mediaPlayer.setDataSource(context, uri);
duration = duration + mediaPlayer.getDuration();
} catch (IOException ignored) {}
mediaPlayer.prepare();
} catch (IOException ignored) {
}
duration = duration + mediaPlayer.getDuration();
mediaPlayer.reset();
}
} else {