[web-src] Add seek forward/backward buttons for podcasts/audiobooks

This commit is contained in:
chme 2020-04-17 06:23:28 +02:00
parent 45e7816637
commit bbacf3e406
7 changed files with 127 additions and 127 deletions

View File

@ -19,8 +19,10 @@
<!-- Skip previous (not visible on "now playing" page) --> <!-- Skip previous (not visible on "now playing" page) -->
<player-button-previous v-if="is_now_playing_page" class="navbar-item fd-margin-left-auto" icon_style="mdi-24px"></player-button-previous> <player-button-previous v-if="is_now_playing_page" class="navbar-item fd-margin-left-auto" icon_style="mdi-24px"></player-button-previous>
<player-button-seek-back v-if="is_now_playing_page" seek_ms="10000" class="navbar-item" icon_style="mdi-24px"></player-button-seek-back>
<!-- Play/pause --> <!-- Play/pause -->
<player-button-play-pause class="navbar-item" icon_style="mdi-36px" show_disabled_message></player-button-play-pause> <player-button-play-pause class="navbar-item" icon_style="mdi-36px" show_disabled_message></player-button-play-pause>
<player-button-seek-forward v-if="is_now_playing_page" seek_ms="30000" class="navbar-item" icon_style="mdi-24px"></player-button-seek-forward>
<!-- Skip next (not visible on "now playing" page) --> <!-- Skip next (not visible on "now playing" page) -->
<player-button-next v-if="is_now_playing_page" class="navbar-item" icon_style="mdi-24px"></player-button-next> <player-button-next v-if="is_now_playing_page" class="navbar-item" icon_style="mdi-24px"></player-button-next>
@ -99,22 +101,11 @@
<hr class="navbar-divider"> <hr class="navbar-divider">
<div class="navbar-item"> <div class="navbar-item">
<div class="level is-mobile fd-expanded"> <div class="level is-mobile fd-expanded">
<div class="level-left"> <div class="level-item">
<div class="level-item"> <div class="buttons has-addons">
<div class="buttons has-addons"> <player-button-repeat class="button"></player-button-repeat>
<player-button-previous class="button"></player-button-previous> <player-button-shuffle class="button"></player-button-shuffle>
<player-button-play-pause class="button"></player-button-play-pause> <player-button-consume class="button"></player-button-consume>
<player-button-next class="button"></player-button-next>
</div>
</div>
</div>
<div class="level-right">
<div class="level-item">
<div class="buttons has-addons">
<player-button-repeat class="button"></player-button-repeat>
<player-button-shuffle class="button"></player-button-shuffle>
<player-button-consume class="button"></player-button-consume>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -166,47 +157,34 @@
</div> </div>
</div> </div>
<!-- Outputs dropdown menu <!-- Outputs: speaker volumes -->
<div class="navbar-item has-dropdown" <navbar-item-output v-for="output in outputs" :key="output.id" :output="output"></navbar-item-output>
:class="{ 'is-active': show_outputs_menu }">
<a class="navbar-link is-arrowless has-text-centered is-size-7" @click="show_outputs_menu = !show_outputs_menu">
<span class="icon"><i class="mdi mdi-18px" :class="{ 'mdi-chevron-up': !show_outputs_menu, 'mdi-chevron-down': show_outputs_menu }"></i></span>
</a>
<div class="navbar-dropdown is-right" v-show="show_outputs_menu"> <!-- Outputs: stream volume -->
<hr class="navbar-divider"> <hr class="navbar-divider">
--> <div class="navbar-item">
<!-- Outputs: speaker volumes --> <div class="level is-mobile">
<navbar-item-output v-for="output in outputs" :key="output.id" :output="output"></navbar-item-output> <div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0;">
<!-- Outputs: stream volume --> <a class="button is-white is-small" :class="{ 'is-loading': loading }"><span class="icon fd-has-action" :class="{ 'has-text-grey-light': !playing && !loading, 'is-loading': loading }" @click="togglePlay"><i class="mdi mdi-18px mdi-radio-tower"></i></span></a>
<hr class="navbar-divider"> </div>
<div class="navbar-item"> <div class="level-item fd-expanded">
<div class="level is-mobile"> <div class="fd-expanded">
<div class="level-left fd-expanded"> <p class="heading" :class="{ 'has-text-grey-light': !playing }">HTTP stream <a href="/stream.mp3"><span class="is-lowercase">(stream.mp3)</span></a></p>
<div class="level-item" style="flex-grow: 0;"> <range-slider
<a class="button is-white is-small" :class="{ 'is-loading': loading }"><span class="icon fd-has-action" :class="{ 'has-text-grey-light': !playing && !loading, 'is-loading': loading }" @click="togglePlay"><i class="mdi mdi-18px mdi-radio-tower"></i></span></a> class="slider fd-has-action"
</div> min="0"
<div class="level-item fd-expanded"> max="100"
<div class="fd-expanded"> step="1"
<p class="heading" :class="{ 'has-text-grey-light': !playing }">HTTP stream <a href="/stream.mp3"><span class="is-lowercase">(stream.mp3)</span></a></p> :disabled="!playing"
<range-slider :value="stream_volume"
class="slider fd-has-action" @change="set_stream_volume">
min="0" </range-slider>
max="100"
step="1"
:disabled="!playing"
:value="stream_volume"
@change="set_stream_volume">
</range-slider>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- </div> </div>
</div> </div>
-->
</div> </div>
</div> </div>
</nav> </nav>
@ -223,12 +201,26 @@ import PlayerButtonPrevious from '@/components/PlayerButtonPrevious'
import PlayerButtonShuffle from '@/components/PlayerButtonShuffle' import PlayerButtonShuffle from '@/components/PlayerButtonShuffle'
import PlayerButtonConsume from '@/components/PlayerButtonConsume' import PlayerButtonConsume from '@/components/PlayerButtonConsume'
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat' import PlayerButtonRepeat from '@/components/PlayerButtonRepeat'
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack'
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward'
import RangeSlider from 'vue-range-slider' import RangeSlider from 'vue-range-slider'
import * as types from '@/store/mutation_types' import * as types from '@/store/mutation_types'
export default { export default {
name: 'NavbarBottom', name: 'NavbarBottom',
components: { NavbarItemLink, NavbarItemOutput, RangeSlider, PlayerButtonPlayPause, PlayerButtonNext, PlayerButtonPrevious, PlayerButtonShuffle, PlayerButtonConsume, PlayerButtonRepeat }, components: {
NavbarItemLink,
NavbarItemOutput,
RangeSlider,
PlayerButtonPlayPause,
PlayerButtonNext,
PlayerButtonPrevious,
PlayerButtonShuffle,
PlayerButtonConsume,
PlayerButtonRepeat,
PlayerButtonSeekForward,
PlayerButtonSeekBack
},
data () { data () {
return { return {

View File

@ -0,0 +1,38 @@
<template>
<a @click="seek" :disabled="disabled" v-if="visible">
<span class="icon"><i class="mdi mdi-rewind" :class="icon_style"></i></span>
</a>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'PlayerButtonSeekBack',
props: ['seek_ms', 'icon_style'],
computed: {
now_playing () {
return this.$store.getters.now_playing
},
is_stopped () {
return this.$store.state.player.state === 'stop'
},
disabled () {
return !this.$store.state.queue || this.$store.state.queue.count <= 0 || this.is_stopped ||
this.now_playing.data_kind === 'pipe'
},
visible () {
return ['podcast', 'audiobook'].includes(this.now_playing.media_kind)
}
},
methods: {
seek: function () {
if (!this.disabled) {
webapi.player_seek(this.seek_ms * -1)
}
}
}
}
</script>

View File

@ -0,0 +1,38 @@
<template>
<a @click="seek" :disabled="disabled" v-if="visible">
<span class="icon"><i class="mdi mdi-fast-forward" :class="icon_style"></i></span>
</a>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'PlayerButtonSeekForward',
props: ['seek_ms', 'icon_style'],
computed: {
now_playing () {
return this.$store.getters.now_playing
},
is_stopped () {
return this.$store.state.player.state === 'stop'
},
disabled () {
return !this.$store.state.queue || this.$store.state.queue.count <= 0 || this.is_stopped ||
this.now_playing.data_kind === 'pipe'
},
visible () {
return ['podcast', 'audiobook'].includes(this.now_playing.media_kind)
}
},
methods: {
seek: function () {
if (!this.disabled) {
webapi.player_seek(this.seek_ms)
}
}
}
}
</script>

View File

@ -1,36 +0,0 @@
<template>
<a v-on:click="play_skip_back">
<i v-if="is_skip_allowed">
<span class="icon"><i class="mdi mdi-replay"></i></span>
</i>
<i v-else>
<span class="icon has-text-grey-light"><i class="mdi mdi-replay"></i></span>
</i>
</a>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'PlayerButtonSkipBack',
props: [ 'when_ms' ],
computed: {
is_skip_allowed () {
return this.$store.state.player.state !== 'stop' && this.$store.getters.now_playing && this.$store.getters.now_playing.data_kind !== 'url' && this.$store.getters.now_playing.data_kind !== 'pipe'
}
},
methods: {
play_skip_back: function () {
if (this.is_skip_allowed) {
webapi.player_seek(this.when_ms - 10000)
}
}
}
}
</script>
<style>
</style>

View File

@ -1,36 +0,0 @@
<template>
<a v-on:click="play_skip_fwd">
<i v-if="is_skip_allowed">
<span class="icon"><i class="mdi mdi-flip-h mdi-replay"></i></span>
</i>
<i v-else>
<span class="icon has-text-grey-light"><i class="mdi mdi-flip-h mdi-replay"></i></span>
</i>
</a>
</template>
<script>
import webapi from '@/webapi'
export default {
name: 'PlayerButtonSkipFwd',
props: [ 'when_ms' ],
computed: {
is_skip_allowed () {
return this.$store.state.player.state !== 'stop' && this.$store.getters.now_playing && this.$store.getters.now_playing.data_kind !== 'url' && this.$store.getters.now_playing.data_kind !== 'pipe'
}
},
methods: {
play_skip_fwd: function () {
if (this.is_skip_allowed) {
webapi.player_seek(this.when_ms + 10000)
}
}
}
}
</script>
<style>
</style>

View File

@ -176,7 +176,7 @@ export default {
}, },
seek: function (newPosition) { seek: function (newPosition) {
webapi.player_seek(newPosition).catch(() => { webapi.player_seek_to_pos(newPosition).catch(() => {
this.item_progress_ms = this.state.item_progress_ms this.item_progress_ms = this.state.item_progress_ms
}) })
}, },

View File

@ -178,10 +178,14 @@ export default {
return axios.put('/api/player/volume?volume=' + outputVolume + '&output_id=' + outputId) return axios.put('/api/player/volume?volume=' + outputVolume + '&output_id=' + outputId)
}, },
player_seek (newPosition) { player_seek_to_pos (newPosition) {
return axios.put('/api/player/seek?position_ms=' + newPosition) return axios.put('/api/player/seek?position_ms=' + newPosition)
}, },
player_seek (seekMs) {
return axios.put('/api/player/seek?seek_ms=' + seekMs)
},
outputs () { outputs () {
return axios.get('/api/outputs') return axios.get('/api/outputs')
}, },