Android: How to create fade-in/fade-out sound effects for any music file that my app plays?
Asked Answered
O

5

25

The application that I am working on plays music files. If a timer expires I want the music to fade out. How do I do that. I am using MediaPlayer to play music and music files are present in raw folder of my application.

Oneida answered 30/7, 2011 at 16:54 Comment(0)
S
24

One way to do it is to use MediaPlayer.setVolume(right, left) and have these values decrement after every iteration..here is a rough idea

float volume = 1;
float speed = 0.05f;

public void FadeOut(float deltaTime)
{
    mediaPlayer.setVolume(volume, volume);
    volume -= speed* deltaTime

}
public void FadeIn(float deltaTime)
{
    mediaPlayer.setVolume(volume, volume);
    volume += speed* deltaTime

}

The FadeIn() or FadeOut() should be called once this timer of yours has expired. The method doesn't need to take the deltaTime, but it's better as it will lower the volume at the same rate across all devices.

Schaffer answered 30/7, 2011 at 18:31 Comment(5)
This is something that I didnt want to do but still ended up doing because of lack of better solution. I was looking for a better and elegant solution but seems that there is none. So I will accept this answer.Oneida
Could you add a little more example code? I think I understand what you're driving at with this example, but it still hasn't clicked for me. Like, where does "deltaTime" come from? Would you do it all in one loop?Lettered
@Schaffer what if i want to use FadeOut and FadeIn at same time for two song? means i want to use FadeOut for song1 and FadeIn for song2 at same time. so how it work???? Can you help me to solve this problem?!!Immunity
how you did that?any clues?Miniver
where to call fade out and where to call fade in? tried calling fadein from onPreparedListener and fadeout from onCompletionListener but it is not working. any ideas ?Miniver
T
30

This is my entire handler class for Android MediaPlayer. Look at the play() and pause() functions. Both contain the ability to either fade or not. The updateVolume() function was the key to let the sound increase/decrease linearly.

package com.stackoverflow.utilities;

import java.io.File;
import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;

public class MusicHandler {
    private MediaPlayer mediaPlayer;
    private Context context;
    private int iVolume;

    private final static int INT_VOLUME_MAX = 100;
    private final static int INT_VOLUME_MIN = 0;
    private final static float FLOAT_VOLUME_MAX = 1;
    private final static float FLOAT_VOLUME_MIN = 0;

    public MusicHandler(Context context) {
        this.context = context;
    }

    public void load(String path, boolean looping) {
        mediaPlayer = MediaPlayer.create(context, Uri.fromFile(new File(path)));
        mediaPlayer.setLooping(looping);
    }

    public void load(int address, boolean looping) {
        mediaPlayer = MediaPlayer.create(context, address);
        mediaPlayer.setLooping(looping);
    }

    public void play(int fadeDuration) {
        // Set current volume, depending on fade or not
        if (fadeDuration > 0)
            iVolume = INT_VOLUME_MIN;
        else
            iVolume = INT_VOLUME_MAX;

        updateVolume(0);

        // Play music
        if (!mediaPlayer.isPlaying())
            mediaPlayer.start();

        // Start increasing volume in increments
        if (fadeDuration > 0) {
            final Timer timer = new Timer(true);
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    updateVolume(1);
                    if (iVolume == INT_VOLUME_MAX) {
                        timer.cancel();
                        timer.purge();
                    }
                }
            };

            // calculate delay, cannot be zero, set to 1 if zero
            int delay = fadeDuration / INT_VOLUME_MAX;
            if (delay == 0)
                delay = 1;

            timer.schedule(timerTask, delay, delay);
        }
    }

    public void pause(int fadeDuration) {
        // Set current volume, depending on fade or not
        if (fadeDuration > 0)
            iVolume = INT_VOLUME_MAX;
        else
            iVolume = INT_VOLUME_MIN;

        updateVolume(0);

        // Start increasing volume in increments
        if (fadeDuration > 0) {
            final Timer timer = new Timer(true);
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    updateVolume(-1);
                    if (iVolume == INT_VOLUME_MIN) {
                        // Pause music
                        if (mediaPlayer.isPlaying())
                            mediaPlayer.pause();
                        timer.cancel();
                        timer.purge();
                    }
                }
            };

            // calculate delay, cannot be zero, set to 1 if zero
            int delay = fadeDuration / INT_VOLUME_MAX;
            if (delay == 0)
                delay = 1;

            timer.schedule(timerTask, delay, delay);
        }
    }

    private void updateVolume(int change) {
        // increment or decrement depending on type of fade
        iVolume = iVolume + change;

        // ensure iVolume within boundaries
        if (iVolume < INT_VOLUME_MIN)
            iVolume = INT_VOLUME_MIN;
        else if (iVolume > INT_VOLUME_MAX)
            iVolume = INT_VOLUME_MAX;

        // convert to float value
        float fVolume = 1 - ((float) Math.log(INT_VOLUME_MAX - iVolume) / (float) Math.log(INT_VOLUME_MAX));

        // ensure fVolume within boundaries
        if (fVolume < FLOAT_VOLUME_MIN)
            fVolume = FLOAT_VOLUME_MIN;
        else if (fVolume > FLOAT_VOLUME_MAX)
            fVolume = FLOAT_VOLUME_MAX;

        mediaPlayer.setVolume(fVolume, fVolume);
    }
}
Thury answered 13/3, 2013 at 19:59 Comment(1)
+1 Thanks for the answer @Thury . It helped!. Just wanted to add that, in my case, since I had to call the play method within a custom util class, I couldn't use a field level variable. I ended up using an AtomicInteger instance for variable "iVolume" instance instead of int iVolume, within the invoking method. (This is because the TimerTask couldn't get the updated value of iVolume for each subsequent call to the run() block)Vieira
S
24

One way to do it is to use MediaPlayer.setVolume(right, left) and have these values decrement after every iteration..here is a rough idea

float volume = 1;
float speed = 0.05f;

public void FadeOut(float deltaTime)
{
    mediaPlayer.setVolume(volume, volume);
    volume -= speed* deltaTime

}
public void FadeIn(float deltaTime)
{
    mediaPlayer.setVolume(volume, volume);
    volume += speed* deltaTime

}

The FadeIn() or FadeOut() should be called once this timer of yours has expired. The method doesn't need to take the deltaTime, but it's better as it will lower the volume at the same rate across all devices.

Schaffer answered 30/7, 2011 at 18:31 Comment(5)
This is something that I didnt want to do but still ended up doing because of lack of better solution. I was looking for a better and elegant solution but seems that there is none. So I will accept this answer.Oneida
Could you add a little more example code? I think I understand what you're driving at with this example, but it still hasn't clicked for me. Like, where does "deltaTime" come from? Would you do it all in one loop?Lettered
@Schaffer what if i want to use FadeOut and FadeIn at same time for two song? means i want to use FadeOut for song1 and FadeIn for song2 at same time. so how it work???? Can you help me to solve this problem?!!Immunity
how you did that?any clues?Miniver
where to call fade out and where to call fade in? tried calling fadein from onPreparedListener and fadeout from onCompletionListener but it is not working. any ideas ?Miniver
N
8

It is a very good class sngreco.

To make it more complete I will add stop() function to stop the player with fade, and stopAndRelease() to stop the player and release the resources securely, very useful to use when you call Activity methods like onStop() or onDestroy().

The two methods:

    public void stop(int fadeDuration)
{
    try {
        // Set current volume, depending on fade or not
        if (fadeDuration > 0)
            iVolume = INT_VOLUME_MAX;
        else
            iVolume = INT_VOLUME_MIN;

        updateVolume(0);

        // Start increasing volume in increments
        if (fadeDuration > 0)
        {
            final Timer timer = new Timer(true);
            TimerTask timerTask = new TimerTask()
            {
                @Override
                public void run()
                {
                    updateVolume(-1);
                    if (iVolume == INT_VOLUME_MIN)
                    {
                        // Pause music
                        mediaPlayer.stop();
                        timer.cancel();
                        timer.purge();
                    }
                }
            };

            // calculate delay, cannot be zero, set to 1 if zero
            int delay = fadeDuration / INT_VOLUME_MAX;
            if (delay == 0)
                delay = 1;

            timer.schedule(timerTask, delay, delay);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public void stopAndRelease(int fadeDuration) {
    try {
        final Timer timer = new Timer(true);
        TimerTask timerTask = new TimerTask()
        {
            @Override
            public void run()
            {
                updateVolume(-1);
                if (iVolume == INT_VOLUME_MIN)
                {
                    // Stop and Release player after Pause music
                    mediaPlayer.stop();
                    mediaPlayer.release();
                    timer.cancel();
                    timer.purge();
                }
            }
        };

        timer.schedule(timerTask, fadeDuration);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Nudd answered 3/1, 2014 at 7:46 Comment(0)
U
5

I have been working on this I hope it helps :D :

private static void crossFade() {
    MediaPlayerManager.fadeOut(currentPlayer, 2000);
    MediaPlayerManager.fadeIn(auxPlayer, 2000);
    currentPlayer = auxPlayer;
    auxPlayer = null;
}

public static void fadeOut(final MediaPlayer _player, final int duration) {
    final float deviceVolume = getDeviceVolume();
    final Handler h = new Handler();
    h.postDelayed(new Runnable() {
        private float time = duration;
        private float volume = 0.0f;

        @Override
        public void run() {
            if (!_player.isPlaying())
                _player.start();
            // can call h again after work!
            time -= 100;
            volume = (deviceVolume * time) / duration;
            _player.setVolume(volume, volume);
            if (time > 0)
                h.postDelayed(this, 100);
            else {
                _player.stop();
                _player.release();
            }
        }
    }, 100); // 1 second delay (takes millis)


}

public static void fadeIn(final MediaPlayer _player, final int duration) {
    final float deviceVolume = getDeviceVolume();
    final Handler h = new Handler();
    h.postDelayed(new Runnable() {
        private float time = 0.0f;
        private float volume = 0.0f;

        @Override
        public void run() {
            if (!_player.isPlaying())
                _player.start();
            // can call h again after work!
            time += 100;
            volume = (deviceVolume * time) / duration;
            _player.setVolume(volume, volume);
            if (time < duration)
                h.postDelayed(this, 100);
        }
    }, 100); // 1 second delay (takes millis)

}
public static float getDeviceVolume() {
    int volumeLevel = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);

    return (float) volumeLevel / maxVolume;
}
Uvulitis answered 25/3, 2015 at 1:38 Comment(1)
what if ii want to use FadeOut and FadeIn at same. I mean I am playing one song and then i press fade button then plying song volume goes down slowly at same time next song playing with volume increase. As first song volume goes to 0 it stop and the second song play continues.Immunity
E
0

Here is my simplified adaptation of stock Android Alarm Clock's fade in implementation.

Rather than defining the number of steps/increments and then increasing the volume step by step (as in other answers to this question), it adjusts volume every 50ms (configurable value) working out steps/increments on the scale between -40dB (near silent) and 0dB (max; relative to the stream volume) based on:

  • Preset effect duration (can be hard-coded or set by user)
  • Elapsed time since the playback started

See computeVolume() below for the juicy bits.

Full original code can be found here: Google Source

private MediaPlayer mMediaPlayer;
private long mCrescendoDuration = 0;
private long mCrescendoStopTime = 0;

// Default settings
private static final boolean DEFAULT_CRESCENDO = true;
private static final int CRESCENDO_DURATION = 1;

// Internal message codes
private static final int EVENT_VOLUME = 3;


// Create a message Handler
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ...

            case EVENT_VOLUME:
            if (adjustVolume()) {
                scheduleVolumeAdjustment();
            }
            break;

            ...
        }
    }
};


// Obtain user preferences
private void getPrefs() {
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    ...

    final boolean crescendo = prefs.getBoolean(SettingsActivity.KEY_CRESCENDO, DEFAULT_CRESCENDO);
    if (crescendo) {
        // Convert mins to millis
        mCrescendoDuration = CRESCENDO_DURATION * 1000 * 60;
    } else {
        mCrescendoDuration = 0;
    }

    ...
}


// Start the playback
private void play(Alarm alarm) {
    ...

    // Check to see if we are already playing
    stop();

    // Obtain user preferences
    getPrefs();

    // Check if crescendo is enabled. If it is, set alarm volume to 0.
    if (mCrescendoDuration > 0) {
        mMediaPlayer.setVolume(0, 0);
    }

    mMediaPlayer.setDataSource(this, alarm.alert);
    startAlarm(mMediaPlayer);

    ...
}


// Do the common stuff when starting the alarm.
private void startAlarm(MediaPlayer player) throws java.io.IOException, IllegalArgumentException, IllegalStateException {

    final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);

    // Do not play alarms if stream volume is 0
    // (typically because ringer mode is silent).
    if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
        player.setAudioStreamType(AudioManager.STREAM_ALARM);
        player.setLooping(true);
        player.prepare();
        player.start();

        // Schedule volume adjustment
        if (mCrescendoDuration > 0) {
            mCrescendoStopTime = System.currentTimeMillis() + mCrescendoDuration;
            scheduleVolumeAdjustment();
        }
    }
}


// Stop the playback
public void stop() {
    ...

    if (mMediaPlayer != null) {
        mMediaPlayer.stop();
        mMediaPlayer.release();
        mMediaPlayer = null;
    }

    mCrescendoDuration = 0;
    mCrescendoStopTime = 0;

    ...
}


// Schedule volume adjustment 50ms in the future.
private void scheduleVolumeAdjustment() {
    // Ensure we never have more than one volume adjustment queued.
    mHandler.removeMessages(EVENT_VOLUME);
    // Queue the next volume adjustment.
    mHandler.sendMessageDelayed( mHandler.obtainMessage(EVENT_VOLUME, null), 50);
}


// Adjusts the volume of the ringtone being played to create a crescendo effect.
private boolean adjustVolume() {
    // If media player is absent or not playing, ignore volume adjustment.
    if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) {
        mCrescendoDuration = 0;
        mCrescendoStopTime = 0;
        return false;
    }
    // If the crescendo is complete set the volume to the maximum; we're done.
    final long currentTime = System.currentTimeMillis();
    if (currentTime > mCrescendoStopTime) {
        mCrescendoDuration = 0;
        mCrescendoStopTime = 0;
        mMediaPlayer.setVolume(1, 1);
        return false;
    }
    // The current volume of the crescendo is the percentage of the crescendo completed.
    final float volume = computeVolume(currentTime, mCrescendoStopTime, mCrescendoDuration);
    mMediaPlayer.setVolume(volume, volume);

    // Schedule the next volume bump in the crescendo.
    return true;
}


/**
 * @param currentTime current time of the device
 * @param stopTime time at which the crescendo finishes
 * @param duration length of time over which the crescendo occurs
 * @return the scalar volume value that produces a linear increase in volume (in decibels)
 */
private static float computeVolume(long currentTime, long stopTime, long duration) {
    // Compute the percentage of the crescendo that has completed.
    final float elapsedCrescendoTime = stopTime - currentTime;
    final float fractionComplete = 1 - (elapsedCrescendoTime / duration);
    // Use the fraction to compute a target decibel between -40dB (near silent) and 0dB (max).
    final float gain = (fractionComplete * 40) - 40;
    // Convert the target gain (in decibels) into the corresponding volume scalar.
    final float volume = (float) Math.pow(10f, gain/20f);
    //LOGGER.v("Ringtone crescendo %,.2f%% complete (scalar: %f, volume: %f dB)", fractionComplete * 100, volume, gain);
    return volume;
}
Eocene answered 10/6, 2018 at 11:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.