/ Gists / 08 Videoplayer (skeleton) Composition API
On gists

08 Videoplayer (skeleton) Composition API

cdruc vue

VideoPlayer.vue Raw #

<script setup>
import axios from "axios";
import Player from "@vimeo/player";
import {throttle} from "lodash";
import ForwardIcon from "./svgs/ForwardIcon";
import BackwardIcon from "./svgs/BackwardIcon";
import LoadingIcon from "./svgs/LoadingIcon";
import {computed, onMounted, onUnmounted, ref, useSlots} from "vue";

const props = defineProps({
  lessonId: Number,
  trackProgress: Boolean
});

const speedIndex = ref(1);
let player = null;
const speedOptions = [0.75, 1, 1.25, 1.5, 1.75, 2.0];
const currentSpeed = computed(() => speedOptions[speedIndex.value]);

if (window.localStorage.getItem('speedIndex')) {
  speedIndex.value = parseInt(window.localStorage.getItem('speedIndex'));
}

onMounted(() => {
  player = new Player(useSlots().default()[0].el);
  player.on('play', () => player.setPlaybackRate(currentSpeed.value));

  if (props.trackProgress) {
    player.on('progress', throttle((event) => updateProgress(event.percent), 10000));
    player.on('ended', () => updateProgress(1));
  }
});

onUnmounted(() => {
  player.off('play');
  player.off('progress');
  player.off('ended');
});

function updateProgress(percent) {
  return axios.put(`/lessons/${props.lessonId}/progress`, {percent: percent});
}

function toggleSpeed() {
  if (typeof speedOptions[speedIndex.value + 1] !== "undefined") {
    speedIndex.value += 1;
  } else {
    speedIndex.value = 0;
  }

  window.localStorage.setItem('speedIndex', speedIndex.value);
  player.setPlaybackRate(currentSpeed.value);
}

async function goForward(seconds) {
  const currentTime = await player.getCurrentTime();
  const totalDuration = await player.getDuration();
  player.setCurrentTime(Math.min(totalDuration, (currentTime + seconds)));
}

async function goBack(seconds) {
  const currentTime = await player.getCurrentTime();
  player.setCurrentTime(Math.max(0, (currentTime - seconds)));
}
</script>

<template>
  <div class="relative aspect-w-16 aspect-h-9">
    <div class="flex absolute inset-0 justify-center items-center">
      <LoadingIcon class="w-14 h-14 text-white animate-spin"></LoadingIcon>
    </div>

    <slot></slot>
  </div>

  <div class="flex justify-end">
    <div class="grid grid-cols-5 w-full max-w-xl divide-x divide-gray-700">
      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="toggleSpeed()">
        <span class="hidden sm:inline-block">Speed:</span><span>{{ currentSpeed }}x</span>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goBack(5)">
        <BackwardIcon class="w-4 h-4"/>
        <span>5s</span>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goForward(5)">
        <span>5s</span>
        <ForwardIcon class="w-4 h-4"/>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goBack(10)">
        <BackwardIcon class="w-4 h-4"/>
        <span>10s</span>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goForward(10)">
        <span>10s</span>
        <ForwardIcon class="w-4 h-4"/>
      </button>
    </div>
  </div>
</template>