Vue - 将 html 音频播放器转换为组件

Posted

技术标签:

【中文标题】Vue - 将 html 音频播放器转换为组件【英文标题】:Vue - Convert html audio player into component 【发布时间】:2019-06-11 18:17:00 【问题描述】:

我正在将 html 中的播放器转换为 Vue 组件。

已经创建了一半的组件,只是缺少时间控制滑块。

这里是html播放器代码(带有多个标签的行已经在Vue组件中实现了):

var audioPlayer = document.querySelector('.green-audio-player');
var playPause = audioPlayer.querySelector('#playPause');
              var playpauseBtn = audioPlayer.querySelector('.play-pause-btn');
var loading = audioPlayer.querySelector('.loading');
var progress = audioPlayer.querySelector('.progress');
var sliders = audioPlayer.querySelectorAll('.slider');
var player = audioPlayer.querySelector('audio');
var currentTime = audioPlayer.querySelector('.current-time');
              var totalTime = audioPlayer.querySelector('.total-time');
var speaker = audioPlayer.querySelector('#speaker');

var draggableClasses = ['pin'];
var currentlyDragged = null;

window.addEventListener('mousedown', function(event) 
  
  if(!isDraggable(event.target)) return false;
  
  currentlyDragged = event.target;
  let handleMethod = currentlyDragged.dataset.method;
  
  this.addEventListener('mousemove', window[handleMethod], false);

  window.addEventListener('mouseup', () => 
    currentlyDragged = false;
    window.removeEventListener('mousemove', window[handleMethod], false);
  , false);  
);

          playpauseBtn.addEventListener('click', togglePlay);
          player.addEventListener('timeupdate', updateProgress);
          player.addEventListener('loadedmetadata', () => 
            totalTime.textContent = formatTime(player.duration);
          );
          player.addEventListener('canplay', makePlay);
          player.addEventListener('ended', function()
            playPause.attributes.d.value = "M18 12L0 24V0";
            player.currentTime = 0;
          );

sliders.forEach(slider => 
  let pin = slider.querySelector('.pin');
  slider.addEventListener('click', window[pin.dataset.method]);
);

function isDraggable(el) 
  let canDrag = false;
  let classes = Array.from(el.classList);
  draggableClasses.forEach(draggable => 
    if(classes.indexOf(draggable) !== -1)
      canDrag = true;
  )
  return canDrag;


function inRange(event) 
  let rangeBox = getRangeBox(event);
  let rect = rangeBox.getBoundingClientRect();
  let direction = rangeBox.dataset.direction;
  if(direction == 'horizontal') 
    var min = rangeBox.offsetLeft;
    var max = min + rangeBox.offsetWidth;   
    if(event.clientX < min || event.clientX > max) return false;
   else 
    var min = rect.top;
    var max = min + rangeBox.offsetHeight; 
    if(event.clientY < min || event.clientY > max) return false;  
  
  return true;


              function updateProgress() 
                var current = player.currentTime;
                var percent = (current / player.duration) * 100;
                progress.style.width = percent + '%';

                currentTime.textContent = formatTime(current);
              

function getRangeBox(event) 
  let rangeBox = event.target;
  let el = currentlyDragged;
  if(event.type == 'click' && isDraggable(event.target)) 
    rangeBox = event.target.parentElement.parentElement;
  
  if(event.type == 'mousemove') 
    rangeBox = el.parentElement.parentElement;
  
  return rangeBox;


function getCoefficient(event) 
  let slider = getRangeBox(event);
  let rect = slider.getBoundingClientRect();
  let K = 0;
  if(slider.dataset.direction == 'horizontal') 
    
    let offsetX = event.clientX - slider.offsetLeft;
    let width = slider.clientWidth;
    K = offsetX / width;    
    
   else if(slider.dataset.direction == 'vertical') 
    
    let height = slider.clientHeight;
    var offsetY = event.clientY - rect.top;
    K = 1 - offsetY / height;
    
  
  return K;


function rewind(event) 
  if(inRange(event)) 
    player.currentTime = player.duration * getCoefficient(event);
  


function formatTime(time) 
  var min = Math.floor(time / 60);
  var sec = Math.floor(time % 60);
  return min + ':' + ((sec<10) ? ('0' + sec) : sec);


              function togglePlay() 
                if(player.paused) 
                  playPause.attributes.d.value = "M0 0h6v24H0zM12 0h6v24h-6z";
                  player.play();
                 else 
                  playPause.attributes.d.value = "M18 12L0 24V0";
                  player.pause();
                  
              

              function makePlay() 
                playpauseBtn.style.display = 'block';
                loading.style.display = 'none';
              
.audio.green-audio-player 
  width: 400px;
  min-width: 300px;
  height: 56px;
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.07);
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 24px;
  padding-right: 24px;
  border-radius: 4px;
  user-select: none;
  -webkit-user-select: none;
  background-color: #fff;

.audio.green-audio-player .play-pause-btn 
  display: none;
  cursor: pointer;

.audio.green-audio-player .spinner 
  width: 18px;
  height: 18px;
  background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/loading.png);
  background-size: cover;
  background-repeat: no-repeat;
  animation: spin 0.4s linear infinite;

.audio.green-audio-player .slider 
  flex-grow: 1;
  background-color: #D8D8D8;
  cursor: pointer;
  position: relative;

.audio.green-audio-player .slider .progress 
  background-color: #44BFA3;
  border-radius: inherit;
  position: absolute;
  pointer-events: none;

.audio.green-audio-player .slider .progress .pin 
  height: 16px;
  width: 16px;
  border-radius: 8px;
  background-color: #44BFA3;
  position: absolute;
  pointer-events: all;
  box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.32);

.audio.green-audio-player .controls 
  font-family: 'Roboto', sans-serif;
  font-size: 16px;
  line-height: 18px;
  color: #55606E;
  display: flex;
  flex-grow: 1;
  justify-content: space-between;
  align-items: center;
  margin-left: 24px;

.audio.green-audio-player .controls .slider 
  margin-left: 16px;
  margin-right: 16px;
  border-radius: 2px;
  height: 4px;

.audio.green-audio-player .controls .slider .progress 
  width: 0;
  height: 100%;

.audio.green-audio-player .controls .slider .progress .pin 
  right: -8px;
  top: -6px;

.audio.green-audio-player .controls span 
  cursor: default;


svg, img 
  display: block;


@keyframes spin 
  from 
    transform: rotateZ(0);
  
  to 
    transform: rotateZ(1turn);
  
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="audio green-audio-player">
  <div class="loading">
    <div class="spinner"></div>
  </div>
  <div class="play-pause-btn">  
    <svg xmlns="http://www.w3.org/2000/svg"   viewBox="0 0 18 24">
      <path fill="#566574" fill-rule="evenodd" d="M18 12L0 24V0" class="play-pause-icon" id="playPause"/>
    </svg>
  </div>
  
  <div class="controls">
    <span class="current-time">0:00</span>
    <div class="slider" data-direction="horizontal">
      <div class="progress">
        <div class="pin" id="progress-pin" data-method="rewind"></div>
      </div>
    </div>
    <span class="total-time">0:00</span>
  </div>
  
  <audio>
    <source src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/Swing_Jazz_Drum.mp3" type="audio/mpeg">
  </audio>
</div>

HTML 代码笔:https://codepen.io/caiokawasaki/pen/JwVwry

这里是 Vue 组件:

Vue.component('audio-player', 
  props: ['message'],
  data: () => (
    audio: undefined,
    loaded: false,
    playing: false,
    currentTime: '00:00',
    totalTime: '00:00',
    percent: '0%',
    draggableClasses: ['pin'],
    currentlyDragged: null
  ),
  computed: ,
  methods: 
    formatTime(time) 
      var min = Math.floor(time / 60);
      var sec = Math.floor(time % 60);
      return min + ':' + ((sec < 10) ? ('0' + sec) : sec);
    ,
    loadedMetaData() 
      this.totalTime = this.formatTime(this.audio.duration)
    ,
    canPlay() 
      this.loaded = true
    ,
    timeUpdate()
      var current = this.audio.currentTime;
      var percent = (current / this.audio.duration) * 100;

      this.percent = percent + '%';

      this.currentTime = this.formatTime(current);
    ,
    ended()
      this.playing = false
      this.audio.currentTime = 0
    ,
    isDraggable(el) 
      let canDrag = false;
      let classes = Array.from(el.classList);
      this.draggableClasses.forEach(draggable => 
        if (classes.indexOf(draggable) !== -1)
          canDrag = true;
      )
      return canDrag;
    ,
    inRange(event) 
      let rangeBox = getRangeBox(event);
      let rect = rangeBox.getBoundingClientRect();
      let direction = rangeBox.dataset.direction;
      if (direction == 'horizontal') 
        var min = rangeBox.offsetLeft;
        var max = min + rangeBox.offsetWidth;
        if (event.clientX < min || event.clientX > max) return false;
       else 
        var min = rect.top;
        var max = min + rangeBox.offsetHeight;
        if (event.clientY < min || event.clientY > max) return false;
      
      return true;
    ,
    togglePlay() 
      if (this.audio.paused) 
        this.audio.play();
        this.playing = true;
       else 
        this.audio.pause();
        this.playing = false;
      
    ,
    makePlay() 
      playpauseBtn.style.display = 'block';
      loading.style.display = 'none';
    ,
    getRangeBox(event) 
      let rangeBox = event.target;
      let el = currentlyDragged;
      if (event.type == 'click' && isDraggable(event.target)) 
        rangeBox = event.target.parentElement.parentElement;
      
      if (event.type == 'mousemove') 
        rangeBox = el.parentElement.parentElement;
      
      return rangeBox;
    ,
    getCoefficient(event) 
      let slider = getRangeBox(event);
      let rect = slider.getBoundingClientRect();
      let K = 0;
      if (slider.dataset.direction == 'horizontal') 

        let offsetX = event.clientX - slider.offsetLeft;
        let width = slider.clientWidth;
        K = offsetX / width;

       else if (slider.dataset.direction == 'vertical') 

        let height = slider.clientHeight;
        var offsetY = event.clientY - rect.top;
        K = 1 - offsetY / height;

      
      return K;
    ,
    rewind(event) 
      if (this.inRange(event)) 
        this.audio.currentTime = this.audio.duration * getCoefficient(event);
      
    
  ,
  mounted() 
    this.audio = this.$refs.audio
  ,
  template: `<div class="audio-message-content">
<a v-if="loaded" class="play-pause-btn" href="#" :title="playing ? 'Clique aqui para pausar o audio' : 'Clique aqui ouvir o audio'" @click.prevent="togglePlay">
<svg key="pause" v-if="playing" x="0px" y="0px" viewBox="0 0 18 20" style="width: 18px; height: 20px; margin-top: -10px">
<path d="M17.1,20c0.49,0,0.9-0.43,0.9-0.96V0.96C18,0.43,17.6,0,17.1,0h-5.39c-0.49,0-0.9,0.43-0.9,0.96v18.07c0,0.53,0.4,0.96,0.9,0.96H17.1z M17.1,20"/>
<path d="M6.29,20c0.49,0,0.9-0.43,0.9-0.96V0.96C7.19,0.43,6.78,0,6.29,0H0.9C0.4,0,0,0.43,0,0.96v18.07C0,19.57,0.4,20,0.9,20H6.29z M6.29,20"/>
</svg>
<svg key="play" v-else x="0px" y="0px" viewBox="0 0 18 22" style="width: 18px; height: 22px; margin-top: -11px">
<path d="M17.45,10.01L1.61,0.14c-0.65-0.4-1.46,0.11-1.46,0.91V20.8c0,0.81,0.81,1.32,1.46,0.91l15.84-9.87C18.1,11.43,18.1,10.41,17.45,10.01L17.45,10.01z M17.45,10.01"/>
</svg>
</a>
<div v-else class="loading">
<div class="spinner"></div>
</div>

<div class="controls">
<span class="current-time"> currentTime </span>
<div class="slider" data-direction="horizontal" @click="">
<div class="progress" :style="width: percent">
<div class="pin" id="progress-pin" data-method="rewind"></div>
</div>
</div>
<span class="total-time"> totalTime </span>
</div>

<audio ref="audio" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/Swing_Jazz_Drum.mp3" @loadedmetadata="loadedMetaData" @canplay="canPlay" @timeupdate="timeUpdate" @ended="ended"></audio>
</div>`
)

var app = new Vue(
  el: '#app'
)
.audio-message-content 
  width: 400px;
  min-width: 300px;
  height: 56px;
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.07);
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 24px;
  padding-right: 24px;
  border-radius: 4px;
  user-select: none;
  -webkit-user-select: none;
  background-color: #fff;

.audio-message-content .play-pause-btn 
  position: relative;
  width: 18px;
  height: 22px;
  cursor: pointer;

.audio-message-content .play-pause-btn svg 
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -9px;

.audio-message-content .spinner 
  width: 18px;
  height: 18px;
  background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/loading.png);
  background-size: cover;
  background-repeat: no-repeat;
  animation: spin 0.4s linear infinite;

.audio-message-content .slider 
  flex-grow: 1;
  background-color: #D8D8D8;
  cursor: pointer;
  position: relative;

.audio-message-content .slider .progress 
  background-color: #44BFA3;
  border-radius: inherit;
  position: absolute;
  pointer-events: none;

.audio-message-content .slider .progress .pin 
  height: 16px;
  width: 16px;
  border-radius: 8px;
  background-color: #44BFA3;
  position: absolute;
  pointer-events: all;
  box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.32);

.audio-message-content .controls 
  font-family: 'Roboto', sans-serif;
  font-size: 16px;
  line-height: 18px;
  color: #55606E;
  display: flex;
  flex-grow: 1;
  justify-content: space-between;
  align-items: center;
  margin-left: 24px;

.audio-message-content .controls .slider 
  margin-left: 16px;
  margin-right: 16px;
  border-radius: 2px;
  height: 4px;

.audio-message-content .controls .slider .progress 
  width: 0;
  height: 100%;

.audio-message-content .controls .slider .progress .pin 
  right: -8px;
  top: -6px;

.audio-message-content .controls span 
  cursor: default;


svg, img 
  display: block;


@keyframes spin 
  from 
    transform: rotateZ(0);
  
  to 
    transform: rotateZ(1turn);
  
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <audio-player></audio-player>
</div>

Vue 组件代码笔:https://codepen.io/caiokawasaki/pen/QzRMwz

以下功能我无法理解,也无法在互联网上找到任何东西:

window[handleMethod]
window[pin.dataset.method]

谁能帮我完成这个组件?

编辑

我已将所有的 html 和 javascript 转换为 Vue 组件,但无论如何它仍然无法正常工作。

唯一不能正常工作的是进度条。它需要执行两个功能:

    单击它应该会转到所需的时间。 单击图钉并将其拖动到所需时间。

我使用Vue Cli,以上两种都不是.vue文件的形式,但是在Codepen中通常写的只有函数2有效。

Codepen:https://codepen.io/caiokawasaki/pen/VqOqBQ

【问题讨论】:

你终于完成了完全转换吗? 【参考方案1】:

函数:window[handleMethod] 通过从pin 元素的data- 属性派生方法的名称来执行:

<div class="pin" id="progress-pin" data-method="rewind"></div>

所以window[handleMethod] 等价于window.rewind()

window[pin.dataset.method] 也是如此。

所以在你的情况下:

this[handleMethod](event)

和:

this[pin.dataset.method](event)

应该是合适的替代品。

【讨论】:

以上是关于Vue - 将 html 音频播放器转换为组件的主要内容,如果未能解决你的问题,请参考以下文章

如何将 PCM 音频流转换为在线播放

JS使用wavesurfer播放网络音频(Vue)

使用 OpenAL 将音频转换为 CAF 格式以便在 iPhone 上播放

支持vue3.0 中的音频插件都有哪些?

如何将mp3等音频文件转换为asf格式?

vue.js实现audio播放amr格式音频