38

I'd like to start and stop HTML5 playback in a random position with fade in and fade out periods to smooth the listening experience.

What kind of mechanisms exists for this? Manually ramp up the volume with setTimeout()?

Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435

5 Answers5

94

The jQuery way...

$audio.animate({volume: newVolume}, 1000);

Edit: where $audio is a jQuery-wrapped audio element and newVolume is a double between 0 and 1.

Edit: The element's effective media volume is volume, interpreted relative to the range 0.0 to 1.0, with 0.0 being silent, and 1.0 being the loudest setting, values in between increasing in loudness. The range need not be linear. http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#effective-media-volume

Edit: People are posting vanilla JavaScript implementations, so I'll post a vanilla TypeScript one that preserves the jQuery swing animation (just strip out the type info if you want to run this in JavaScript). Disclaimer, this is completely untested:

export async function adjustVolume(
    element: HTMLMediaElement,
    newVolume: number,
    {
        duration = 1000,
        easing = swing,
        interval = 13,
    }: {
        duration?: number,
        easing?: typeof swing,
        interval?: number,
    } = {},
): Promise<void> {
    const originalVolume = element.volume;
    const delta = newVolume - originalVolume;

    if (!delta || !duration || !easing || !interval) {
        element.volume = newVolume;
        return Promise.resolve();
    }

    const ticks = Math.floor(duration / interval);
    let tick = 1;

    return new Promise(resolve => {
        const timer = setInterval(() => {
            element.volume = originalVolume + (
                easing(tick / ticks) * delta
            );

            if (++tick === ticks + 1) {
                clearInterval(timer);
                resolve();
            }
        }, interval);
    });
}

export function swing(p: number) {
    return 0.5 - Math.cos(p * Math.PI) / 2;
}
ffxsam
  • 26,428
  • 32
  • 94
  • 144
jedmao
  • 10,224
  • 11
  • 59
  • 65
  • And you've just saved me a bunch of time and confusion. Thanks! – Sofox Feb 15 '13 at 12:28
  • N00b question, but what would go into the newVolume function? I am guessing it's a 5 step volume increase or so? – Kanishka Ganguly Mar 08 '14 at 21:07
  • @KanishkaGanguly it's a double between 0 and 1 http://html5doctor.com/html5-audio-the-state-of-play/ – jedmao Mar 09 '14 at 23:11
  • Whomever voted this down might want to elaborate as to why you don't like the trick. – jedmao Sep 22 '14 at 23:47
  • 1
    "newVolume is a double between 0 and 1" That means assign it a value like `newVolume=0.2`? @mrjedmao – Faisal Ashfaq Oct 12 '14 at 05:31
  • @FaisalAshfaq it's all in the HTML5 spec – The element's effective media volume is volume, interpreted relative to the range 0.0 to 1.0, with 0.0 being silent, and 1.0 being the loudest setting, values in between increasing in loudness. The range need not be linear. http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#effective-media-volume – jedmao Oct 12 '14 at 21:35
  • wow, after years of using Jquery, it still surprises me with its completeness. Never would've guessed this actually worked. I've been using Jplayer for this stuff, and it's totally unnecessary if you don't actually care about the dinosaur Adobe Flash fallback. – Abram Apr 05 '15 at 12:28
  • 1
    Is this an asynchronous call? – Wes Modes Apr 18 '15 at 04:31
  • 1
    @WesModes Yes, but you can add a callback function to the end of the argument list if you want something to happen once the fade is completed. – Inkling Dec 03 '15 at 01:27
  • what does it mean to wrap an audio element in jquery ($audio)? is that referring to something like the range slider's ID? or the variable that identifies the HTMLaudioelement object? – mrtunes Jul 20 '20 at 19:20
  • `const $audio = $(audioElement);` makes `$audio` a jQuery-wrapped audio element. Only then can you call the `.animate()` method on it. – jedmao Sep 11 '20 at 18:12
  • @jedmao Why did you pick 13 for the interval? – ffxsam Oct 31 '20 at 23:18
  • @ffxsam just add "linear" as a 3rd argument to `animate`. https://api.jquery.com/animate/ https://jqueryui.com/easing/ – jedmao Dec 05 '20 at 16:50
  • Fixed a small bug. The ticks comparison should be `tick === ticks + 1`, otherwise I noticed the target volume was never quite reached. – ffxsam Jan 27 '21 at 23:18
31

Old question but if anyone is looking for a vanilla JS way to do this I just wrote something up for project and thought I'd post it here since my search for a solution was in vain. If you are already working with a video or audio element, there's a good chance you don't really need to use jQuery to control the object anyways.

function getSoundAndFadeAudio (audiosnippetId) {

    var sound = document.getElementById(audiosnippetId);

    // Set the point in playback that fadeout begins. This is for a 2 second fade out.
    var fadePoint = sound.duration - 2; 

    var fadeAudio = setInterval(function () {

        // Only fade if past the fade out point or not at zero already
        if ((sound.currentTime >= fadePoint) && (sound.volume != 0.0)) {
            sound.volume -= 0.1;
        }
        // When volume at zero stop all the intervalling
        if (sound.volume === 0.0) {
            clearInterval(fadeAudio);
        }
    }, 200);

}

This version doesn't allow for editing the fadeout time (set to 2 seconds) but you could pretty easily argumentize it. To fully generisize this, extra logic would be needed to also first check what the volume was set to in order to know the factor by which to fade it out. In our case, we preset the volume to 1 already and browser volume control is out of the users hands as it's for a slideshow thing so it wasn't needed.

To get to a specific part of the audio you'd want to check the seekable timerange and just set the currentTime randomly based on what's available.

user801985
  • 413
  • 4
  • 3
4

I created a simple lib for this using setTimeout().

Source code is available here:

https://github.com/miohtama/Krusovice/blob/master/src/tools/fade.js

Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435
3

Here are a couple of functions to fade-out current song and fade-in new song.

HTML:

<audio loop autoplay="1" onPlay="audioVolumeIn(this);">
         <source src="/test/new/imgs/audio_bg.ogg" type="audio/ogg; codecs=vorbis">
         <source src="/test/new/imgs/audio_bg.wav" type="audio/wav">
         <source src="/test/new/imgs/audio_bg.mp3" type="audio/mpeg">
</audio>

Javascript:

    function audioVolumeIn(q){
       if(q.volume){
          var InT = 0;
          var setVolume = 0.2; // Target volume level for new song
          var speed = 0.005; // Rate of increase
          q.volume = InT;
          var eAudio = setInterval(function(){
              InT += speed;
              q.volume = InT.toFixed(1);
              if(InT.toFixed(1) >= setVolume){
                 clearInterval(eAudio);
                 //alert('clearInterval eAudio'+ InT.toFixed(1));
              };
          },50);
       };
   };
   
   function audioVolumeOut(q){
       if(q.volume){
          var InT = 0.4;
          var setVolume = 0;  // Target volume level for old song 
          var speed = 0.005;  // Rate of volume decrease
          q.volume = InT;
          var fAudio = setInterval(function(){
              InT -= speed;
              q.volume = InT.toFixed(1);
              if(InT.toFixed(1) <= setVolume){
                 clearInterval(fAudio);
                 //alert('clearInterval fAudio'+ InT.toFixed(1));
              };
          },50);
       };
   };
Leonel
  • 825
  • 11
  • 19
user3126867
  • 610
  • 5
  • 8
-2

This is a simple timeout function for fading the audio (from 0 to 100 in this case). It uses a global variable. Still struggling with a whole package though. I'll definitely be checking out sfjedi's jQuery way.

function FadeIn() {

    var sound = document.getElementById('audiosnippet');

    var vol = $.global.volume;

    if ( vol < 100 )
        {
            sound.volume = (vol / 100);
            $.global.volume = $.global.volume + 10;
            setInterval(function() { FadeIn() }, 1200);
        }

    }
}
David G
  • 94,763
  • 41
  • 167
  • 253
jibbon
  • 29
  • 3
  • Why such a high timeout? you could fade a lot more smoothly by decreasing both the timeout and the volume delta. – andrewrk Jul 28 '13 at 02:24
  • 2
    I think you mean to use `setTimeout` instead of `setInterval`? In your case you are creating more and more intervals every time you call `FadeIn()` which never get canceled. Eventually they just run in the back and do nothing once the volume reaches `100`. – Mathias Aug 20 '13 at 19:09