hw-gs-player

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

Player.vue (6817B)


      1 <script setup lang="ts">
      2 import { YoutubeIframe } from '@vue-youtube/component';
      3 import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
      4 import queue from '../queue';
      5 import Tracklist from './TrackList.vue';
      6 import SvgIcon from '@jamescoyle/vue-icon';
      7 import { mdiRepeatOnce, mdiRepeatOff, mdiStarCheck, mdiStarPlusOutline, mdiSkipPrevious, mdiSkipNext } from '@mdi/js';
      8 import { useI18n } from 'vue-i18n';
      9 
     10 // define Emit event
     11 const emit = defineEmits(["update:isFullscreen"]);
     12 
     13 // YouTube player instance
     14 const player = ref<any>(null);
     15 
     16 // Video time
     17 const nowTime = ref(0);
     18 const endTime = ref(0);
     19 const intervalId = ref<number | null>(null);
     20 const isPlaying = ref(false);
     21 
     22 // Album and song info
     23 const { t, locale } = useI18n();
     24 const albumTitle = ref(t(queue.get_albumTitle())); // 初期値を翻訳キーで設定
     25 
     26 // 現在の曲名をリアクティブに取得
     27 const songTitle = computed(() => {
     28   const nowSong = queue.get_nowSong();
     29   return t(nowSong.SongName); // 翻訳キーを使用して曲名を取得
     30 });
     31 
     32 // 言語変更時にアルバムタイトルを更新
     33 watch(locale, () => {
     34   albumTitle.value = t(queue.get_albumTitle());
     35 });
     36 
     37 // Fullscreen mode
     38 const isFullscreen = ref(false);
     39 const animationClass = ref("");
     40 const fscontainer = ref<HTMLDivElement | null>(null);
     41 
     42 // Toggle fullscreen mode
     43 const toggleFullscreen = () => {
     44   isFullscreen.value = !isFullscreen.value;
     45   emit("update:isFullscreen", isFullscreen.value);
     46 };
     47 
     48 // Watch fullscreen state changes
     49 watch(isFullscreen, (newVal, oldVal) => {
     50   if (newVal !== oldVal) {
     51     animationClass.value = newVal ? "slide-up" : "slide-down";
     52 
     53     // Remove animation class after transition
     54     setTimeout(() => {
     55       animationClass.value = "";
     56     }, 250);
     57     adjustScale();
     58   }
     59 });
     60 
     61 // Toggle play/pause
     62 const togglePlay = async () => {
     63   player.value?.togglePlay();
     64 };
     65 
     66 // When player is ready
     67 const onReady = (event: { target: any }) => {
     68   endTime.value = Math.floor(event.target.getDuration());
     69 };
     70 
     71 // Handle video state changes
     72 const onStateChange = (event: { target: any; data: number }) => {
     73   if (intervalId.value !== null) {
     74     clearInterval(intervalId.value);
     75     intervalId.value = null;
     76   }
     77 
     78   if (event.data === 1) { // Playing
     79     isPlaying.value = true;
     80     intervalId.value = window.setInterval(() => {
     81       nowTime.value = Math.floor(event.target.getCurrentTime());
     82     }, 200);
     83   } else if (event.data === 0) { // End
     84     isPlaying.value = false;
     85     queue.get_next();
     86   } else if (event.data === 2) { // Paused
     87     isPlaying.value = false;
     88   }
     89 
     90   albumTitle.value = t(queue.get_albumTitle()); // 言語変更時にアルバムタイトルを更新
     91 };
     92 
     93 // Format time as mm:ss
     94 const formatTime = (time: number) => {
     95   const minutes = Math.floor(time / 60);
     96   const seconds = time % 60;
     97   return `${minutes}:${seconds.toString().padStart(2, '0')}`;
     98 };
     99 
    100 // Computed formatted time
    101 const formattedNowTime = computed(() => formatTime(nowTime.value));
    102 const formattedEndTime = computed(() => formatTime(endTime.value));
    103 
    104 // Adjust scale
    105 const adjustScale = async () => {
    106   await nextTick();
    107 
    108   if (!isFullscreen.value) { // Reset scale if not full screen
    109     document.body.style.transform = "";
    110     document.body.style.transformOrigin = "";
    111     return;
    112   }
    113 
    114   if (fscontainer.value) {
    115     const containerHeight = fscontainer.value.offsetHeight;
    116     const windowHeight = window.innerHeight;
    117     const scaleFactor = Math.min(1, windowHeight / containerHeight);
    118 
    119     document.body.style.transform = `scale(${scaleFactor})`; // Apply scale to body
    120     document.body.style.transformOrigin = "top center"; // scaling origin point
    121   }
    122 };
    123 
    124 // Add event listeners for resize and orientation change
    125 onMounted(() => {
    126   window.addEventListener("resize", adjustScale); // Recalculate scale when resizing window
    127   window.addEventListener("orientationchange", adjustScale); // Recalculate scale when changing screen orientation
    128   adjustScale();
    129 });
    130 
    131 // Remove event listeners on unmount
    132 onUnmounted(() => {
    133   window.removeEventListener("resize", adjustScale);
    134   window.removeEventListener("orientationchange", adjustScale);
    135   if (intervalId.value !== null) {
    136     clearInterval(intervalId.value);
    137   }
    138 });
    139 
    140 const renderKey = queue.get_playerRenderKey();
    141 
    142 </script>
    143 
    144 <template>
    145   <div class="layout">
    146     <div 
    147       ref="fscontainer" 
    148       :class="[isFullscreen ? 'fscontainer' : 'container', animationClass]" 
    149       :key="renderKey"
    150     >
    151       <youtube-iframe
    152         :video-id="queue.get_nowSong().videoId"
    153         :player-vars="{
    154           autoplay: 1,
    155           iv_load_policy: 3,
    156           loop: 0,
    157           controls: 0,
    158           playsinline: 1,
    159           rel: 0
    160         }"
    161         ref="player"
    162         @ready="onReady" 
    163         @state-change="onStateChange" 
    164       />
    165 
    166       <div class="info_box">
    167         <!-- Video title -->
    168         <div class="title" @click="toggleFullscreen">
    169           {{ songTitle }}
    170         </div>
    171 
    172         <!-- Time and album info -->
    173         <div class="time_container">
    174           <div class="nowtime">{{ formattedNowTime }}</div>
    175           <div class="endtime">{{ formattedEndTime }}</div>
    176           <div class="album" @click="toggleFullscreen">{{ albumTitle }}</div>
    177         </div> 
    178 
    179         <div class="fs_album_container">
    180           <div class="fs_album" @click="toggleFullscreen">{{ albumTitle }}</div>
    181           <div class="fs_loop" @click="queue.onLoopButton()">
    182             <SvgIcon type="mdi" :path="queue.isLoop.value ? mdiRepeatOnce : mdiRepeatOff" />
    183           </div>
    184           <div class="fs_fav" @click="queue.event_favorite(queue.get_nowSong())">
    185             <SvgIcon type="mdi" :path="queue.isFavorite.value ? mdiStarCheck : mdiStarPlusOutline" />
    186           </div>
    187         </div>
    188 
    189         <div class="fs_time_container">
    190           <div class="nowtime">{{ formattedNowTime }}</div>
    191           <div class="endtime">{{ formattedEndTime }}</div>
    192         </div>
    193       </div>
    194 
    195       <div class="control_box">
    196         <!-- Prev button -->
    197         <div class="prev-button" @click="queue.get_next()">
    198           <SvgIcon type="mdi" :path="mdiSkipPrevious" />
    199         </div>
    200 
    201         <!-- Play/Pause button -->
    202         <button @click="togglePlay" class="play-pause-button">
    203           <div class="play-pause-icon" :class="{ playing: isPlaying }">
    204             <div class="bar bar-left"></div>
    205             <div class="bar bar-right"></div>
    206           </div>
    207         </button>
    208 
    209         <!-- Next button -->
    210         <div class="next-button" @click="queue.get_next()">
    211           <SvgIcon type="mdi" :path="mdiSkipNext" />
    212         </div>
    213       </div>
    214     </div>
    215 
    216     <div 
    217       :class="[isFullscreen ? 'fstracklist-container' : 'tracklist-container', animationClass]"
    218     >
    219       <Tracklist />
    220     </div>
    221   </div>
    222 </template>
    223 
    224 <style scoped>
    225 @import '../player-style.css';
    226 </style>