A New Collection of Thoughtful Learning Apps — Now Available on iOS & Android

Image
I’m excited to share a set of mobile apps I’ve recently completed and published on both the Google Play Store and the Apple App Store. These apps are designed with a simple goal in mind: to make meaningful, structured content more accessible, whether you’re studying theology or improving your English vocabulary. 📱 Now Available on Both Platforms All apps are live and available for download: Google Play Developer Page: https://play.google.com/store/apps/dev?id=5835943159853189043 Apple App Store Developer Page: https://apps.apple.com/ca/developer/q-z-l-corp/id1888794100 📖 Theology & Confession Study Apps For those interested in Reformed theology and classical Christian teachings, I’ve developed a series of apps that present foundational texts in a clean, focused reading format: The Belgic Confession Canons of Dort Heidelberg Catechism Westminster Shorter Catechism Each app is designed to provide a distraction-free experience, making it easier to read, reflect, and revisit these im...

A simple web audio player, bind to <span/>, one click, audio play, click again, audio stop

example

All you have to is to write below html code:
a span with class name of word-audio and attribute of data-src pointed to a audio stream resource
 
<span class='word-audio audio' style='display:inline-block' 
data-src='https://cdn.mp3xa.pw/proxy/cs1-43v4.vkuseraudio.net/
p17/fe6d95af2cee33.mp3'></span> 
 

Bertie Higgins — Casablanca

word.js

  
  function startAnimation(e) {
    if (e.className == 'word-audio audio')
      e.className = 'word-audio audio-light';
    else if (e.className == 'word-audio audio-light')
      e.className = 'word-audio audio-playing';
    else
      e.className = 'word-audio audio'
    console.log(e.className);
  }
  function play(e, context, audioBuffer) {
    if (e.state == 1) {
      e.source.stop();
      e.source.onended();
      e.source = null;
    } else {
      e.state = 1;
      const source = context.createBufferSource();
      e.source = source;
      source.buffer = audioBuffer;
      source.connect(context.destination);
      source.start();
      let it = setInterval(function() {startAnimation(e)}, 300);    
      source.onended = function() {
        e.state = 0;
        clearInterval(it);
        e.className = 'word-audio audio';
      }
    }
  }
  document.querySelectorAll('.word-audio').forEach(function(e, index) {
    let url = e.attributes['data-src'].nodeValue;
    let context = new AudioContext();
    e.state = 0;
    let wordBuffer;
    window.fetch(url)
      .then(response => response.arrayBuffer())
      .then(arrayBuffer => context.decodeAudioData(arrayBuffer))
      .then(audioBuffer => {
        e.disabled = false;
        wordBuffer = audioBuffer;
        //play(e, context, wordBuffer);
      });
    e.onclick = function() {
      play(e, context, wordBuffer);       
    }
  });

word.css

.audio {
    display: inline-block;
    width: 20px;
    height: 20px;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    vertical-align: middle;
    background: url(audio.png) no-repeat -40px 0/auto 100%;
}

.audio-playing {
    display: inline-block;
    width: 20px;
    height: 20px;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    vertical-align: middle;
    background: url(audio.png) no-repeat -20px 0/auto 100%;
}

.audio-light {
    display: inline-block;
    width: 20px;
    height: 20px;
    position: relative;
    overflow: hidden;
    cursor: pointer;
    vertical-align: middle;
    background: url(audio.png) no-repeat 0px 0/auto 100%;
}

❤️ Support This Blog


If this post helped you, you can support my writing with a small donation. Thank you for reading.


Comments

Popular Posts

2026 Begins: Choosing to Stay on the Path as a Blogger

Health Checks and Scaling Strategies for Next.js in Kubernetes