mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			124 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<template>
 | 
						|
  <div ref="wrapper" class="relative ml-4 sm:ml-8" v-click-outside="clickOutside">
 | 
						|
    <div class="flex items-center justify-center text-gray-300 cursor-pointer h-full" @mousedown.prevent @mouseup.prevent @click="setShowMenu(true)">
 | 
						|
      <span class="text-gray-200 text-sm sm:text-base">{{ playbackRateDisplay }}<span class="text-base">x</span></span>
 | 
						|
    </div>
 | 
						|
    <div v-show="showMenu" class="absolute -top-[5.5rem] z-20 bg-bg border-black-200 border shadow-xl rounded-lg" :style="{ left: menuLeft + 'px' }">
 | 
						|
      <div class="absolute -bottom-1.5 right-0 w-full flex justify-center" :style="{ left: arrowLeft + 'px' }">
 | 
						|
        <div class="arrow-down" />
 | 
						|
      </div>
 | 
						|
      <div class="flex items-center h-9 relative overflow-hidden rounded-lg" style="width: 220px">
 | 
						|
        <template v-for="rate in rates">
 | 
						|
          <div :key="rate" class="h-full border-black-300 w-11 cursor-pointer border rounded-xs" :class="value === rate ? 'bg-black-100' : 'hover:bg-black/10'" style="min-width: 44px; max-width: 44px" @click="set(rate)">
 | 
						|
            <div class="w-full h-full flex justify-center items-center">
 | 
						|
              <p class="text-xs text-center">{{ rate }}<span class="text-sm">x</span></p>
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
        </template>
 | 
						|
      </div>
 | 
						|
      <div class="w-full py-1 px-1">
 | 
						|
        <div class="flex items-center justify-between">
 | 
						|
          <ui-icon-btn :disabled="!canDecrement" icon="remove" @click="decrement" />
 | 
						|
          <p class="px-2 text-2xl sm:text-3xl">{{ playbackRateDisplay }}<span class="text-2xl">x</span></p>
 | 
						|
          <ui-icon-btn :disabled="!canIncrement" icon="add" @click="increment" />
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
    </div>
 | 
						|
  </div>
 | 
						|
</template>
 | 
						|
 | 
						|
<script>
 | 
						|
export default {
 | 
						|
  props: {
 | 
						|
    value: {
 | 
						|
      type: [String, Number],
 | 
						|
      default: 1
 | 
						|
    },
 | 
						|
    playbackRateIncrementDecrement: {
 | 
						|
      type: Number,
 | 
						|
      default: 0.1
 | 
						|
    }
 | 
						|
  },
 | 
						|
  data() {
 | 
						|
    return {
 | 
						|
      showMenu: false,
 | 
						|
      currentPlaybackRate: 0,
 | 
						|
      MIN_SPEED: 0.5,
 | 
						|
      MAX_SPEED: 10,
 | 
						|
      menuLeft: -96,
 | 
						|
      arrowLeft: 0
 | 
						|
    }
 | 
						|
  },
 | 
						|
  computed: {
 | 
						|
    playbackRate: {
 | 
						|
      get() {
 | 
						|
        return this.value
 | 
						|
      },
 | 
						|
      set(val) {
 | 
						|
        this.$emit('input', val)
 | 
						|
      }
 | 
						|
    },
 | 
						|
    rates() {
 | 
						|
      return [0.5, 1, 1.2, 1.5, 2]
 | 
						|
    },
 | 
						|
    canIncrement() {
 | 
						|
      return this.playbackRate + this.playbackRateIncrementDecrement <= this.MAX_SPEED
 | 
						|
    },
 | 
						|
    canDecrement() {
 | 
						|
      return this.playbackRate - this.playbackRateIncrementDecrement >= this.MIN_SPEED
 | 
						|
    },
 | 
						|
    playbackRateDisplay() {
 | 
						|
      if (this.playbackRateIncrementDecrement == 0.05) return this.playbackRate.toFixed(2)
 | 
						|
      // For 0.1 increment: Only show 2 decimal places if the playback rate is 2 decimals
 | 
						|
      const numDecimals = String(this.playbackRate).split('.')[1]?.length || 0
 | 
						|
      if (numDecimals <= 1) return this.playbackRate.toFixed(1)
 | 
						|
      return this.playbackRate.toFixed(2)
 | 
						|
    }
 | 
						|
  },
 | 
						|
  methods: {
 | 
						|
    clickOutside() {
 | 
						|
      this.setShowMenu(false)
 | 
						|
    },
 | 
						|
    set(rate) {
 | 
						|
      this.playbackRate = Number(rate)
 | 
						|
      this.$nextTick(() => this.setShowMenu(false))
 | 
						|
    },
 | 
						|
    increment() {
 | 
						|
      if (this.playbackRate + this.playbackRateIncrementDecrement > this.MAX_SPEED) return
 | 
						|
      var newPlaybackRate = this.playbackRate + this.playbackRateIncrementDecrement
 | 
						|
      this.playbackRate = Number(newPlaybackRate.toFixed(2))
 | 
						|
    },
 | 
						|
    decrement() {
 | 
						|
      if (this.playbackRate - this.playbackRateIncrementDecrement < this.MIN_SPEED) return
 | 
						|
      var newPlaybackRate = this.playbackRate - this.playbackRateIncrementDecrement
 | 
						|
      this.playbackRate = Number(newPlaybackRate.toFixed(2))
 | 
						|
    },
 | 
						|
    updateMenuPositions() {
 | 
						|
      if (!this.$refs.wrapper) return
 | 
						|
      const boundingBox = this.$refs.wrapper.getBoundingClientRect()
 | 
						|
 | 
						|
      if (boundingBox.left + 110 > window.innerWidth - 10) {
 | 
						|
        this.menuLeft = window.innerWidth - 230 - boundingBox.left
 | 
						|
 | 
						|
        this.arrowLeft = Math.abs(this.menuLeft) - 96
 | 
						|
      } else {
 | 
						|
        this.menuLeft = -96
 | 
						|
        this.arrowLeft = 0
 | 
						|
      }
 | 
						|
    },
 | 
						|
    setShowMenu(val) {
 | 
						|
      if (val) {
 | 
						|
        this.updateMenuPositions()
 | 
						|
        this.currentPlaybackRate = this.playbackRate
 | 
						|
      } else if (this.currentPlaybackRate !== this.playbackRate) {
 | 
						|
        this.$emit('change', this.playbackRate)
 | 
						|
      }
 | 
						|
      this.showMenu = val
 | 
						|
    }
 | 
						|
  },
 | 
						|
  mounted() {
 | 
						|
    this.currentPlaybackRate = this.playbackRate
 | 
						|
  }
 | 
						|
}
 | 
						|
</script>
 |