owntone-server/web-src/src/components/NavbarBottom.vue

347 lines
12 KiB
Vue
Raw Normal View History

<template>
2022-05-20 07:44:22 -04:00
<nav class="fd-bottom-navbar navbar is-white is-fixed-bottom" :style="zindex" :class="{ 'is-transparent': is_now_playing_page, 'is-dark': !is_now_playing_page }" role="navigation" aria-label="player controls">
<div class="navbar-brand fd-expanded">
<!-- Link to queue -->
<navbar-item-link to="/" exact>
2022-05-20 07:44:22 -04:00
<mdicon class="icon" name="playlist-play" size="24" />
</navbar-item-link>
<!-- Now playing artist/title (not visible on "now playing" page) -->
2022-05-20 07:44:22 -04:00
<router-link v-if="!is_now_playing_page" to="/now-playing" class="navbar-item is-expanded is-clipped" active-class="is-active" exact>
<div class="is-clipped">
<p class="is-size-7 fd-is-text-clipped">
2022-05-20 07:44:22 -04:00
<strong v-text="now_playing.title" />
<br />
<span v-text="now_playing.artist" />
<span v-if="now_playing.data_kind === 'url'" v-text="$t('navigation.now-playing', { album: now_playing.album })" />
</p>
</div>
</router-link>
<!-- Skip previous (not visible on "now playing" page) -->
2022-05-20 07:44:22 -04:00
<player-button-previous v-if="is_now_playing_page" class="navbar-item fd-margin-left-auto" :icon_size="24" />
<player-button-seek-back v-if="is_now_playing_page" :seek_ms="10000" class="navbar-item" :icon_size="24" />
<!-- Play/pause -->
2022-05-20 07:44:22 -04:00
<player-button-play-pause class="navbar-item" :icon_size="36" show_disabled_message />
<player-button-seek-forward v-if="is_now_playing_page" :seek_ms="30000" class="navbar-item" :icon_size="24" />
<!-- Skip next (not visible on "now playing" page) -->
2022-05-20 07:44:22 -04:00
<player-button-next v-if="is_now_playing_page" class="navbar-item" :icon_size="24" />
<!-- Player menu button (only visible on mobile and tablet) -->
2022-05-20 07:44:22 -04:00
<a class="navbar-item fd-margin-left-auto is-hidden-desktop" @click="show_player_menu = !show_player_menu">
<mdicon class="icon" :name="show_player_menu ? 'chevron-down' : 'chevron-up'" size="18" />
</a>
<!-- Player menu dropup menu (only visible on desktop) -->
2022-05-20 07:44:22 -04:00
<div class="navbar-item has-dropdown has-dropdown-up fd-margin-left-auto is-hidden-touch" :class="{ 'is-active': show_player_menu }">
<a class="navbar-link is-arrowless" @click="show_player_menu = !show_player_menu">
<mdicon class="icon" :name="show_player_menu ? 'chevron-down' : 'chevron-up'" size="18" />
</a>
2022-05-20 07:44:22 -04:00
<div class="navbar-dropdown is-right is-boxed" style="margin-right: 6px; margin-bottom: 6px; border-radius: 6px">
<div class="navbar-item">
<!-- Outputs: master volume -->
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0">
2022-05-20 07:44:22 -04:00
<a class="button is-white is-small" @click="toggle_mute_volume">
<mdicon class="icon" :name="player.volume > 0 ? 'volume-high' : 'volume-off'" size="18" />
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
2022-05-20 07:44:22 -04:00
<p class="heading" v-text="$t('navigation.volume')" />
<Slider v-model="player.volume" :min="0" :max="100" :step="1" :tooltips="false" :classes="{ target: 'slider' }" @change="set_volume" />
</div>
</div>
</div>
</div>
</div>
<!-- Outputs: master volume -->
<hr class="fd-navbar-divider" />
2022-05-20 07:44:22 -04:00
<navbar-item-output v-for="output in outputs" :key="output.id" :output="output" />
<!-- Outputs: stream volume -->
<hr class="fd-navbar-divider" />
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0">
2022-05-20 07:44:22 -04:00
<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">
<mdicon name="broadcast" size="18" />
</span>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
2022-05-20 07:44:22 -04:00
<p class="heading" :class="{ 'has-text-grey-light': !playing }">
<span v-text="$t('navigation.stream')" />
<a href="stream.mp3" style="margin-left: 5px" target="_blank">
<mdicon class="icon" name="open-in-new" size="16" style="vertical-align: middle" />
</a>
</p>
2022-05-20 07:44:22 -04:00
<Slider v-model="stream_volume" :min="0" :max="100" :step="1" :tooltips="false" :disabled="!playing" :classes="{ target: 'slider' }" @change="set_stream_volume" />
</div>
</div>
</div>
</div>
</div>
<!-- Playback controls -->
<hr class="fd-navbar-divider" />
<div class="navbar-item">
<div class="level is-mobile fd-expanded">
<div class="level-item">
<div class="buttons has-addons">
<player-button-repeat class="button" />
<player-button-shuffle class="button" />
<player-button-consume class="button" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Player menu (only visible on mobile and tablet) -->
2022-05-20 07:44:22 -04:00
<div class="navbar-menu is-hidden-desktop" :class="{ 'is-active': show_player_menu }">
<div class="navbar-start" />
<div class="navbar-end">
<!-- Repeat/shuffle/consume -->
<div class="navbar-item">
<div class="buttons is-centered">
<player-button-repeat class="button" :icon_size="18" />
<player-button-shuffle class="button" :icon_size="18" />
<player-button-consume class="button" :icon_size="18" />
</div>
</div>
<hr class="fd-navbar-divider" />
<!-- Outputs: master volume -->
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0">
<a class="button is-white is-small" @click="toggle_mute_volume">
2022-05-20 07:44:22 -04:00
<mdicon class="icon" :name="player.volume > 0 ? 'volume-high' : 'volume-off'" size="18" />
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
2022-05-20 07:44:22 -04:00
<p class="heading" v-text="$t('navigation.volume')" />
<Slider v-model="player.volume" :min="0" :max="100" :step="1" :tooltips="false" :classes="{ target: 'slider' }" @change="set_volume" />
</div>
</div>
</div>
</div>
</div>
<!-- Outputs: speaker volumes -->
2022-05-20 07:44:22 -04:00
<navbar-item-output v-for="output in outputs" :key="output.id" :output="output" />
<!-- Outputs: stream volume -->
<hr class="fd-navbar-divider" />
<div class="navbar-item fd-has-margin-bottom">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0">
2022-05-20 07:44:22 -04:00
<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">
<mdicon name="broadcast" size="16" />
</span>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
2022-05-20 07:44:22 -04:00
<p class="heading" :class="{ 'has-text-grey-light': !playing }">
<span v-text="$t('navigation.stream')" />
<a href="stream.mp3" style="margin-left: 5px" target="_blank">
<mdicon class="icon" name="open-in-new" size="16" style="vertical-align: middle" />
</a>
</p>
2022-05-20 07:44:22 -04:00
<Slider v-model="stream_volume" :min="0" :max="100" :step="1" :tooltips="false" :disabled="!playing" :classes="{ target: 'slider' }" @change="set_stream_volume" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</template>
<script>
import webapi from '@/webapi'
import _audio from '@/audio'
2022-02-19 00:18:01 -05:00
import NavbarItemLink from './NavbarItemLink.vue'
import NavbarItemOutput from './NavbarItemOutput.vue'
import PlayerButtonPlayPause from '@/components/PlayerButtonPlayPause.vue'
import PlayerButtonNext from '@/components/PlayerButtonNext.vue'
import PlayerButtonPrevious from '@/components/PlayerButtonPrevious.vue'
import PlayerButtonShuffle from '@/components/PlayerButtonShuffle.vue'
import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue'
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue'
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue'
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue'
import Slider from '@vueform/slider'
import * as types from '@/store/mutation_types'
export default {
name: 'NavbarBottom',
components: {
NavbarItemLink,
NavbarItemOutput,
2022-02-19 00:18:01 -05:00
Slider,
PlayerButtonPlayPause,
PlayerButtonNext,
PlayerButtonPrevious,
PlayerButtonShuffle,
PlayerButtonConsume,
PlayerButtonRepeat,
PlayerButtonSeekForward,
PlayerButtonSeekBack
},
data() {
return {
old_volume: 0,
playing: false,
loading: false,
stream_volume: 10,
show_outputs_menu: false,
show_desktop_outputs_menu: false
}
},
computed: {
show_player_menu: {
get() {
return this.$store.state.show_player_menu
},
set(value) {
this.$store.commit(types.SHOW_PLAYER_MENU, value)
}
},
show_burger_menu() {
return this.$store.state.show_burger_menu
},
zindex() {
if (this.show_burger_menu) {
return 'z-index: 20'
}
return ''
},
state() {
return this.$store.state.player
},
now_playing() {
return this.$store.getters.now_playing
},
is_now_playing_page() {
return this.$route.path === '/now-playing'
},
outputs() {
return this.$store.state.outputs
},
player() {
return this.$store.state.player
},
config() {
return this.$store.state.config
}
},
watch: {
'$store.state.player.volume'() {
if (this.player.volume > 0) {
this.old_volume = this.player.volume
}
}
},
// on app mounted
mounted() {
this.setupAudio()
},
// on app destroyed
unmounted() {
this.closeAudio()
},
methods: {
on_click_outside_outputs() {
this.show_outputs_menu = false
},
set_volume: function (newVolume) {
webapi.player_volume(newVolume)
},
toggle_mute_volume: function () {
if (this.player.volume > 0) {
this.set_volume(0)
} else {
this.set_volume(this.old_volume)
}
},
setupAudio: function () {
const a = _audio.setupAudio()
a.addEventListener('waiting', (e) => {
this.playing = false
this.loading = true
})
a.addEventListener('playing', (e) => {
this.playing = true
this.loading = false
})
a.addEventListener('ended', (e) => {
this.playing = false
this.loading = false
})
a.addEventListener('error', (e) => {
this.closeAudio()
this.$store.dispatch('add_notification', {
2022-05-20 07:44:22 -04:00
text: this.$t('navigation.stream-error'),
type: 'danger'
})
this.playing = false
this.loading = false
})
},
// close active audio
closeAudio: function () {
_audio.stopAudio()
this.playing = false
},
playChannel: function () {
if (this.playing) {
return
}
const channel = '/stream.mp3'
this.loading = true
_audio.playSource(channel)
_audio.setVolume(this.stream_volume / 100)
},
togglePlay: function () {
if (this.loading) {
return
}
if (this.playing) {
return this.closeAudio()
}
return this.playChannel()
},
set_stream_volume: function (newVolume) {
this.stream_volume = newVolume
_audio.setVolume(this.stream_volume / 100)
}
}
}
</script>
<style></style>