feat: volume and seek shortcuts

This commit is contained in:
Phan An 2024-03-18 18:51:51 +01:00
parent 132b91ef19
commit d579677f2e
6 changed files with 52 additions and 19 deletions

View file

@ -137,6 +137,11 @@ or a textarea.
* <kbd>j</kbd> plays the next song in queue
* <kbd>k</kbd> plays the previous song in queue
* <kbd>l</kbd> marks/unmarks the current song as favorite
* <kbd></kbd> seeks forward 10 seconds
* <kbd></kbd> seeks backward 10 seconds
* <kbd></kbd> increases volume by 10%
* <kbd></kbd> decreases volume by 10%
* <kbd>m</kbd> mutes/unmutes
* <kbd>Cmd/Ctrl</kbd>+<kbd>a</kbd> selects all songs in the current song-list screen when the list is focused
* <kbd>Delete</kbd> removes selected song(s) from the current queue/playlist

View file

@ -23,7 +23,7 @@ import { computed, nextTick, ref, watch } from 'vue'
import { eventBus, isAudioContextSupported, requireInjection } from '@/utils'
import { CurrentSongKey } from '@/symbols'
import { artistStore, preferenceStore } from '@/stores'
import { audioService, playbackService, volumeManager } from '@/services'
import { audioService, playbackService } from '@/services'
import AudioPlayer from '@/components/layout/app-footer/AudioPlayer.vue'
import SongInfo from '@/components/layout/app-footer/FooterSongInfo.vue'
@ -57,16 +57,14 @@ const styles = computed(() => {
const initPlaybackRelatedServices = async () => {
const plyrWrapper = document.querySelector<HTMLElement>('.plyr')
const volumeInput = document.querySelector<HTMLInputElement>('#volumeInput')
if (!plyrWrapper || !volumeInput) {
if (!plyrWrapper) {
await nextTick()
await initPlaybackRelatedServices()
return
}
playbackService.init(plyrWrapper)
volumeManager.init(volumeInput)
isAudioContextSupported && audioService.init(playbackService.player.media)
}

View file

@ -23,14 +23,13 @@
</span>
<input
id="volumeInput"
ref="inputEl"
class="plyr__volume"
max="10"
role="slider"
step="0.1"
title="Volume"
type="range"
@change="onVolumeChanged"
@input="setVolume"
>
</span>
@ -38,10 +37,12 @@
<script lang="ts" setup>
import { faVolumeHigh, faVolumeLow, faVolumeMute } from '@fortawesome/free-solid-svg-icons'
import { computed } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { socketService, volumeManager } from '@/services'
import { preferenceStore } from '@/stores'
import { watchThrottled } from '@vueuse/core'
const inputEl = ref<HTMLInputElement>()
const volume = volumeManager.volume
const level = computed(() => {
@ -54,13 +55,12 @@ const mute = () => volumeManager.mute()
const unmute = () => volumeManager.unmute()
const setVolume = (e: Event) => volumeManager.set(parseFloat((e.target as HTMLInputElement).value))
/**
* Broadcast the volume changed event to remote controller.
*/
const onVolumeChanged = (e: Event) => {
preferenceStore.volume = parseFloat((e.target as HTMLInputElement).value)
socketService.broadcast('SOCKET_VOLUME_CHANGED', parseFloat((e.target as HTMLInputElement).value))
}
watchThrottled(volumeManager.volume, volume => {
preferenceStore.volume = volume
socketService.broadcast('SOCKET_VOLUME_CHANGED', volume)
}, { throttle: 1_000 })
onMounted(() => volumeManager.init(inputEl.value!, preferenceStore.volume))
</script>
<style lang="scss">

View file

@ -5,7 +5,7 @@
<script lang="ts" setup>
import { KeyFilter, onKeyStroke as baseOnKeyStroke } from '@vueuse/core'
import { eventBus } from '@/utils'
import { playbackService, socketService } from '@/services'
import { playbackService, socketService, volumeManager } from '@/services'
import { favoriteStore, queueStore } from '@/stores'
const onKeyStroke = (key: KeyFilter, callback: (e: KeyboardEvent) => void) => {
@ -21,6 +21,12 @@ onKeyStroke('j', () => playbackService.playNext())
onKeyStroke('k', () => playbackService.playPrev())
onKeyStroke(' ', () => playbackService.toggle())
onKeyStroke('ArrowRight', () => playbackService.seekBy(10))
onKeyStroke('ArrowLeft', () => playbackService.seekBy(-10))
onKeyStroke('ArrowUp', () => volumeManager.increase())
onKeyStroke('ArrowDown', () => volumeManager.decrease())
onKeyStroke('m', () => volumeManager.toggleMute())
onKeyStroke('l', () => {
if (!queueStore.current) return
favoriteStore.toggleOne(queueStore.current)

View file

@ -304,6 +304,12 @@ class PlaybackService {
this.pause()
}
public seekBy (seconds: number) {
if (this.player.media.duration) {
this.player.media.currentTime += seconds
}
}
/**
* Queue up songs (replace them into the queue) and start playing right away.
*

View file

@ -1,13 +1,14 @@
import { ref } from 'vue'
import { preferenceStore } from '@/stores'
export class VolumeManager {
private input!: HTMLInputElement
private originalVolume = 0
public volume = ref(0)
public init (input: HTMLInputElement) {
public init (input: HTMLInputElement, initialVolume: number) {
this.input = input
this.set(preferenceStore.volume)
this.originalVolume = initialVolume
this.set(initialVolume)
}
public get () {
@ -20,11 +21,28 @@ export class VolumeManager {
}
public mute () {
this.originalVolume = this.get()
this.set(0)
}
public unmute () {
this.set(preferenceStore.volume)
this.set(this.originalVolume)
}
public toggleMute () {
if (this.get() === 0) {
this.unmute()
} else {
this.mute()
}
}
public increase (amount = 1) {
this.set(Math.min(10, this.get() + amount))
}
public decrease (amount = 1) {
this.set(Math.max(this.get() - amount, 0))
}
}