<!-- - @copyright Copyright (c) 2021 Jonas Heinrich - - @author Jonas Heinrich <onny@project-insanity.org> - - @license GNU AGPL version 3 or any later version - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - --> <template> <div id="app-settings"> <div v-lazy:background-image="getEpisodeImage" class="playerThumb" /> <input v-model="seek" class="seek" type="range" name="seek" min="0" :max="player.duration" step=".5" @change="seekEpisode($event.target.value)"> <div class="playbackInfo"> <span>{{ seekHHMMSS }}</span> <span>{{ durationHHMMSS }}</span> </div> <span class="playerTitle">{{ getEpisodeTitle }}</span> <div class="playerControls"> <div class="seekButton seekPrev" @click="seekEpisode(seek - 10)" /> <div class="wrap" :class="{ buffering: player.isBuffering }"> <button class="player" :class="player.isPlaying ? 'pause' : 'play'" @click="togglePlay" /> </div> <div class="seekButton seekNext" @click="seekEpisode(seek + 10)" /> </div> <div class="volumeControls"> <div class="volumeIcon" :class="player.volume == 0 ? 'volumeMute' : 'volumeFull'" @click="toggleMute" /> <input class="volume" type="range" name="volume" min="0" max="1" step=".05" :value="player.volume" @input="changeVolume($event)" @change="saveVolume($event)"> </div> </div> </template> <script> import { mapGetters } from 'vuex' export default { computed: { ...mapGetters([ 'episodeTitle', 'episodeImage', 'playerVolume', ]), player() { return this.$store.state.player }, seek: { get() { return this.$store.state.player.seek }, set(value) { this.$store.state.player.seek = value }, }, getEpisodeTitle() { const title = this.episodeTitle return title }, getEpisodeImage() { const image = this.episodeImage return image }, getPlayerVolume() { const volume = this.playerVolume return volume }, durationHHMMSS() { const seconds = this.player.duration return new Date(seconds * 1000).toISOString().substr(11, 8) }, seekHHMMSS() { const seconds = this.seek return new Date(seconds * 1000).toISOString().substr(11, 8) }, }, methods: { changeVolume() { this.$store.dispatch('setVolume', event.target.value) }, saveVolume() { this.$store.dispatch('saveVolume', event.target.value) }, seekEpisode(time) { this.$store.dispatch('seekEpisode', time) }, toggleMute() { this.$store.dispatch('toggleMute') }, togglePlay() { this.$store.dispatch('togglePlay') }, }, } </script> <style> #app-settings { display: flex; flex-direction: column; align-items: center; } .playerThumb { width: 200px; height: 200px; background: #ddd; background-size: cover; background-position: center; } .seek { width: 200px; } .playbackInfo { display: flex; width: 200px; margin-top: -5px; } .playbackInfo span { width: 100%; } .playbackInfo span:nth-child(2) { text-align: right; } .playerTitle { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: bold; } .playerControls { display: flex; align-items: center; justify-content: center; } .seekButton { width: 35px; height: 35px; border: 2px solid #0082c9; border-radius: 50%; cursor: pointer; background-repeat: no-repeat; background-position: 40% 55%; } .seekNext { background-image: var(--icon-play-next-000); background-position: 50% 55%; } .seekPrev { background-image: var(--icon-play-previous-000); } .wrap { background: var(--color-main-background); border: 3px solid #0082c9; float: left; border-radius: 50%; margin: 10px; } .player{ height:50px; width: 50px; background-color: #0082c9; mask-repeat: no-repeat; mask-size: 55%; mask-position: 70% 50%; } .play{ mask-image: var(--icon-play-000); transition: mask-image 0.4s ease-in-out; } .pause{ mask-image: var(--icon-pause-000); mask-position: 58% 50%; transition: mask-image 0.4s ease-in-out; } .buffering { border: 3px solid #0082c9; animation: buffering 2s infinite linear; } @keyframes buffering { 0% { border-color: #0082c9; } 50% { border-color: var(--color-main-background); } 100% { border-color: #0082c9; } } .volumeControls { display: flex; align-items: center; justify-content: center; margin-bottom: 10px; } .volumeIcon { width: 25px; height: 25px; cursor: pointer; margin-right: 10px; } .volumeFull { background-color: #0082c9; mask-repeat: no-repeat; mask-size: 100%; mask-image: var(--icon-sound-000); } .volumeMute { background-color: #0082c9; mask-repeat: no-repeat; mask-size: 100%; mask-image: var(--icon-sound-off-000); } .volume{ width: 165px; } </style>