JSFiddle - React, Tailwind, and code Playground

by dumptyd

HTML

<link rel="stylesheet" href="https://bootswatch.com/cosmo/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<audio src="http://api.soundcloud.com/tracks/329825886/stream?client_id=f8dc78d527a14157cce82626661b9607&.mp3" id="audio-el"></audio>
<div class="container text-center">
  <div class="player text-left">
    <div class="screen"></div>
    <div class="controls">
      <div class="control pause-play valign halign pull-left">
        <div id="play">
          <i class="fa fa-play"></i>
        </div>
      </div>
      <div class="control i-progress valign ">
        <div class="progress">
          <div class="progress-bar"></div>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.player {
  display: inline-block;
  box-sizing: border-box;
  width: 80%;
  min-width: 400px;
  max-width: 700px;
  height: 300px;
  margin-top: 50px;
  border: 2px solid #999;
  position: relative;
}

.player > .screen {
  height: 90%;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-color: #555;
}

.player > .controls {
  height: 10%;
  border-top: 2px solid #999;
  padding: 0 10px;
  width: 100%;
  position: relative;
}

.controls > .control {
  cursor: pointer;
  height: 100%;
}

.pause-play {
  width: 10%
}

.i-progress {
  width: 90%;
}

.i-progress > .progress {
  margin-bottom: 0;
  width: 100%;
}
.i-progress > .progress > .progress-bar {
  transition: width 50ms linear;
}

.valign {
  display: flex;
  align-items: center;
}

.halign {
  justify-content: center;
}

JavaScript

$(document).ready(function() {
  const images = new Array(10).fill(0).map((_, i) => `https://dummyimage.com/600x400/000/fff.png&text=Slide ${i + 1}`);
  const audioEl = document.querySelector('#audio-el'),
    duration = audioEl.duration;
  let last = 0;
  const dataArr = images.map((img, idx) => {
    const obj = {
      timestamp: {
        start: last,
        end: (duration / images.length) + last
      },
      img
    };
    last = obj.timestamp.end;
    return obj;
  });

  const state = {
    playing: false,
    currentSlideIdx: 0,
    playingSlideIdx: -1
  };
  const utils = {
    setSlide(idx) {
      if(idx === state.playingSlideIdx) return;
      $('.screen').css('background-image', `url('${dataArr[idx].img}')`);
      state.playingSlideIdx = state.currentSlideIdx;
    },
    timeUpdated() {
    	const ct = audioEl.currentTime;
      for (let i = 0; i < dataArr.length; ++i) {
      	const ts = dataArr[i].timestamp;
      	if (ts.end >= ct && ts.start <= ct) {
          state.currentSlideIdx = i;
        	utils.setSlide(i);
          break;
        }
      }
    }
  };
  
  
  audioEl.volume = 0.1;
  audioEl.playbackRate = 1;
  const intervalHandler = function() {
    const ct = audioEl.currentTime;
    const width = (ct / duration) * 100;
    $('.i-progress > .progress > .progress-bar').css('width', `${width}%`);
    if (ct > dataArr[state.currentSlideIdx].timestamp.end) state.currentSlideIdx++;
    if (state.currentSlideIdx !== state.playingSlideIdx) {
      utils.setSlide(state.currentSlideIdx);
    }
  };
  let intervalID = setInterval(intervalHandler, 50);
 
  $('#play').click(function() {
    if (state.playing) {
      $('#play').html('<i class="fa fa-play">');
      audioEl.pause();
    } else {
      $('#play').html('<i class="fa fa-pause">');
      audioEl.play();
    }
    state.playing = !state.playing;
  });
  audioEl.onended = function() {
  	$('#play').html('<i class="fa fa-play">');
    state.playing = false;
  };
  $('.i-progress >...