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>