<v-img\r
class="white--text"\r
width="100%"\r
- height="370"\r
+ :height="isMobile() ? '300' : '370'"\r
position="center top" \r
:src="getFanartImage()"\r
gradient="to top right, rgba(100,115,201,.33), rgba(25,32,72,.7)"\r
\r
<!-- tech specs and provider icons -->\r
<div style="margin-top:10px;">\r
- <a v-for="(value, key) in info.provider_ids" :href="info.metadata[key + '_url']" target="_blank" :key="key">\r
- <img height="30" :src="'/images/icons/' + key + '.png'" style="padding-right:5px" />\r
- </a>\r
- <div style="text-shadow: 1px 1px #000000;vertical-align:top;margin-left:30px;margin-top:-35px;">\r
- <qualityicon v-if="info.media_type == 3" v-bind:item="info" :height="30" :compact="false"/>\r
- </div>\r
+ <providericons v-bind:item="info" :height="30" :compact="false"/>\r
</div>\r
</v-flex>\r
\r
<a style="color:#2196f3" v-on:click="clickItem(artist)">{{ artist.name }}</a>\r
<label style="color:#2196f3" v-if="artistindex + 1 < info.artists.length" :key="artistindex"> / </label>\r
</span>\r
- <span v-if="!!info.artist" class="headline">\r
- <a style="color:#2196f3" v-on:click="clickItem(artist)">{{ info.artist.name }}</a>\r
+ <span v-if="info.artist" class="headline">\r
+ <a style="color:#2196f3" v-on:click="clickItem(info.artist)">{{ info.artist.name }}</a>\r
</span>\r
- <span v-if="!!info.owner" class="headline">\r
+ <span v-if="info.owner" class="headline">\r
<a style="color:#2196f3" v-on:click="">{{ info.owner }}</a>\r
</span>\r
</v-card-title>\r
methods: { \r
getFanartImage() {\r
var img = '';\r
+ if (!this.info)\r
+ return ''\r
if (this.info.metadata && this.info.metadata.fanart)\r
img = this.info.metadata.fanart;\r
else if (this.info.artists)\r
if (artist.metadata && artist.metadata.fanart)\r
img = artist.metadata.fanart;\r
});\r
+ else if (this.info.artist && this.info.artist.metadata.fanart)\r
+ img = this.info.artist.metadata.fanart;\r
return img;\r
},\r
getThumb() {\r
var img = '';\r
+ if (!this.info)\r
+ return ''\r
if (this.info.metadata && this.info.metadata.image)\r
img = this.info.metadata.image;\r
else if (this.info.album && this.info.album.metadata && this.info.album.metadata.image)\r
},\r
getDescription() {\r
var desc = '';\r
+ if (!this.info)\r
+ return ''\r
if (this.info.metadata && this.info.metadata.description)\r
return this.info.metadata.description;\r
else if (this.info.metadata && this.info.metadata.biography)\r
<label v-if="!hidetracknum && item.track_number" style="color:grey"> - disc {{ item.disc_number }} track {{ item.track_number }}</label>
</v-list-tile-sub-title>
<v-list-tile-sub-title v-if="item.artist">
- <a v-on:click="clickItem(artist)" @click.stop="">{{ item.artist.name }}</a>
+ <a v-on:click="clickItem(item.artist)" @click.stop="">{{ item.artist.name }}</a>
</v-list-tile-sub-title>
<v-list-tile-sub-title v-if="!!item.owner">
</v-list-tile-content>
- <qualityicon v-if="item.media_type == 3" v-bind:item="item" :height="25" :compact="true" :dark="true" :hiresonly="true"/>
-
- <v-list-tile-action v-if="!hideproviders" v-for="provider in item.provider_ids" :key="provider.provider + provider.item_id">
- <v-tooltip bottom>
- <template v-slot:activator="{ on }">
- <img v-on="on" height="20" :src="'images/icons/' + provider.provider + '.png'"/>
- </template>
- <span v-if="provider.details">{{ provider.details }}</span>
- <span v-if="!provider.details">{{ provider.quality }}</span>
- </v-tooltip>
- </v-list-tile-action>
+ <providericons v-bind:item="item" :height="20" :compact="true" :dark="true" :hiresonly="hideproviders"/>
<v-list-tile-action v-if="!hidelibrary">
<v-tooltip bottom>
`,
props: ['item', 'index', 'totalitems', 'hideavatar', 'hidetracknum', 'hideproviders', 'hidemenu', 'hidelibrary', 'hideduration'],
data() {
- return {
- selected: [2],
- items: [],
- offset: 0,
- }
+ return {}
},
methods: {
}
<!-- players side menu -->
<v-navigation-drawer right app clipped temporary v-model="menu">
<v-card-title class="headline">
- <b>Players</b>
+ <b>{{ $t('players') }}</b>
</v-card-title>
<v-list two-line>
<v-divider></v-divider>
<v-list-tile-title class="title">{{ player.name }}</v-list-tile-title>
<v-list-tile-sub-title v-if="player.cur_item" class="body-1" :key="player.state">
- {{ player.state }}
+ {{ $t('state.' + player.state) }}
</v-list-tile-sub-title>
</v-list-tile-content>
</div>
</v-list>
</v-navigation-drawer>
- <playmenu v-model="$globals.showplaymenu" v-on:playItem="playItem"/>
+ <playmenu v-model="$globals.showplaymenu" v-on:playItem="playItem" :active_player="active_player" />
</div>
`,
// TODO: store previous player in local storage
if (!this.active_player_id)
for (var player_id in this.players)
- if (this.players[player_id].state == 'playing' && this.players[player_id].enabled) {
+ if (this.players[player_id].state == 'playing' && this.players[player_id].enabled && !this.players[player_id].group_parent) {
// prefer the first playing player
this.active_player_id = player_id;
break;
if (!this.active_player_id)
for (var player_id in this.players) {
// fallback to just the first player
- if (this.players[player_id].enabled)
+ if (this.players[player_id].enabled && !this.players[player_id].group_parent)
{
this.active_player_id = player_id;
break;
<v-dialog :value="value" @input="$emit('input', $event)" max-width="500px" v-if="$globals.playmenuitem">\r
<v-card>\r
<v-list>\r
- <v-subheader>{{ !!$globals.playmenuitem ? $globals.playmenuitem.name : 'nix' }}</v-subheader>\r
- <v-subheader>Play on: beneden</v-subheader>\r
+ <v-subheader class="title">{{ !!$globals.playmenuitem ? $globals.playmenuitem.name : 'nix' }}</v-subheader>\r
+ <v-subheader>{{ $t('play_on') }} {{ active_player.name }}</v-subheader>\r
\r
- <v-list-tile avatar @click="$emit('playItem', $globals.playmenuitem, 'play')">\r
+ <v-list-tile avatar @click="itemClick('play')">\r
<v-list-tile-avatar>\r
<v-icon>play_circle_outline</v-icon>\r
</v-list-tile-avatar>\r
<v-list-tile-content>\r
- <v-list-tile-title>Play Now</v-list-tile-title>\r
+ <v-list-tile-title>{{ $t('play_now') }}</v-list-tile-title>\r
</v-list-tile-content>\r
</v-list-tile>\r
<v-divider></v-divider>\r
\r
- <v-list-tile avatar @click="$emit('playItem', $globals.playmenuitem, 'replace')">\r
- <v-list-tile-avatar>\r
- <v-icon>play_circle_outline</v-icon>\r
- </v-list-tile-avatar>\r
- <v-list-tile-content>\r
- <v-list-tile-title>Replace</v-list-tile-title>\r
- </v-list-tile-content>\r
- </v-list-tile>\r
- <v-divider></v-divider>\r
-\r
- <v-list-tile avatar @click="$emit('playItem', $globals.playmenuitem, 'next')">\r
+ <v-list-tile avatar @click="itemClick('next')">\r
<v-list-tile-avatar>\r
<v-icon>queue_play_next</v-icon>\r
</v-list-tile-avatar>\r
<v-list-tile-content>\r
- <v-list-tile-title>Play Next</v-list-tile-title>\r
+ <v-list-tile-title>{{ $t('play_next') }}</v-list-tile-title>\r
</v-list-tile-content>\r
</v-list-tile>\r
<v-divider></v-divider>\r
\r
- <v-list-tile avatar @click="$emit('playItem', $globals.playmenuitem, 'add')">\r
+ <v-list-tile avatar @click="itemClick('add')">\r
<v-list-tile-avatar>\r
<v-icon>playlist_add</v-icon>\r
</v-list-tile-avatar>\r
<v-list-tile-content>\r
- <v-list-tile-title>Add to Queue</v-list-tile-title>\r
+ <v-list-tile-title>{{ $t('add_queue') }}</v-list-tile-title>\r
</v-list-tile-content>\r
</v-list-tile>\r
<v-divider></v-divider>\r
\r
- <v-list-tile avatar @click="" v-if="$globals.playmenuitem.media_type != 3">\r
- <v-list-tile-avatar>\r
- <v-icon>shuffle</v-icon>\r
- </v-list-tile-avatar>\r
- <v-list-tile-content>\r
- <v-list-tile-title>Play now (shuffle)</v-list-tile-title>\r
- </v-list-tile-content>\r
- </v-list-tile>\r
- <v-divider v-if="$globals.playmenuitem.media_type != 3"/>\r
-\r
- <v-list-tile avatar @click="" v-if="$globals.playmenuitem.media_type == 3">\r
+ <v-list-tile avatar @click="itemClick('info')" v-if="$globals.playmenuitem.media_type == 3">\r
<v-list-tile-avatar>\r
<v-icon>info</v-icon>\r
</v-list-tile-avatar>\r
<v-list-tile-content>\r
- <v-list-tile-title>Show info</v-list-tile-title>\r
+ <v-list-tile-title>{{ $t('show_info') }}</v-list-tile-title>\r
</v-list-tile-content>\r
</v-list-tile>\r
<v-divider v-if="$globals.playmenuitem.media_type == 3"/>\r
</v-card>\r
</v-dialog>\r
`,\r
- props: ['value'],\r
+ props: ['value', 'active_player'],\r
data (){\r
return{\r
fav: true,\r
},\r
mounted() { },\r
created() { },\r
- methods: { }\r
+ methods: { \r
+ itemClick(cmd) {\r
+ if (cmd == 'info')\r
+ this.$router.push({ path: '/tracks/' + this.$globals.playmenuitem.item_id, params: {provider: this.$globals.playmenuitem.provider}})\r
+ else\r
+ this.$emit('playItem', this.$globals.playmenuitem, cmd)\r
+ // close dialog\r
+ this.$globals.showplaymenu = false;\r
+ },\r
+ }\r
})\r
--- /dev/null
+Vue.component("providericons", {\r
+ template: `\r
+ <div :style="'height:' + height + 'px;'">\r
+ <span v-for="provider in uniqueProviders" :key="provider.item_id" style="padding:5px;vertical-align: middle;" v-if="!hiresonly || provider.quality > 6">\r
+ <v-tooltip bottom>\r
+ <template v-slot:activator="{ on }">\r
+ <img v-on="on" :height="height" src="images/icons/hires.png" v-if="provider.quality > 6" style="margin-right:9px"/>\r
+ <img v-on="on" :height="height" :src="'images/icons/' + provider.provider + '.png'" v-if="!hiresonly"/>\r
+ </template>\r
+ <div align="center" v-if="item.media_type == 3">\r
+ <img height="35px" :src="getFileFormatLogo(provider)"/>\r
+ <span><br>{{ getFileFormatDesc(provider) }}</span>\r
+ </div>\r
+ <span v-if="item.media_type != 3">{{ provider.provider }}</span>\r
+ </v-tooltip> \r
+ </span> \r
+ </div>\r
+`,\r
+ props: ['item','height','compact', 'dark', 'hiresonly'],\r
+ data (){\r
+ return{}\r
+ },\r
+ mounted() { },\r
+ created() { },\r
+ computed: {\r
+ uniqueProviders() {\r
+ var keys = [];\r
+ var qualities = [];\r
+ if (!this.item || !this.item.provider_ids)\r
+ return []\r
+ let sorted_item_ids = this.item.provider_ids.sort((a,b) => (a.quality < b.quality) ? 1 : ((b.quality < a.quality) ? -1 : 0));\r
+ if (!this.compact)\r
+ return sorted_item_ids;\r
+ for (provider of sorted_item_ids) {\r
+ if (!keys.includes(provider.provider)){\r
+ qualities.push(provider);\r
+ keys.push(provider.provider);\r
+ }\r
+ }\r
+ return qualities;\r
+ }\r
+ },\r
+ methods: { \r
+\r
+ getFileFormatLogo(provider) {\r
+ if (provider.quality == 0)\r
+ return 'images/icons/mp3.png'\r
+ else if (provider.quality == 1)\r
+ return 'images/icons/vorbis.png'\r
+ else if (provider.quality == 2)\r
+ return 'images/icons/aac.png'\r
+ else if (provider.quality > 2)\r
+ return 'images/icons/flac.png'\r
+ },\r
+ getFileFormatDesc(provider) {\r
+ var desc = '';\r
+ if (provider.details)\r
+ desc += ' ' + provider.details;\r
+ return desc;\r
+ },\r
+ getMaxQualityFormatDesc() {\r
+ var desc = '';\r
+ if (provider.details)\r
+ desc += ' ' + provider.details;\r
+ return desc;\r
+ }\r
+ }\r
+ })\r
+++ /dev/null
-Vue.component("qualityicon", {\r
- template: `\r
- <div :style="'height:' + height + 'px;'">\r
- \r
- <v-tooltip bottom>\r
- <template v-slot:activator="{ on }">\r
- <img height="100%" v-on="on" v-if="item.metadata && item.metadata.hires" src="images/icons/hires.png" style="align:center;vertical-align: middle;padding-right:10px;padding-left:10px;"/>\r
- <img height="100%" v-on="on" v-if="!dark && !hiresonly" :src="getFileFormatLogo()" style="align:center;vertical-align: middle;"/>\r
- <img height="100%" v-on="on" v-if="dark && !hiresonly" :src="getFileFormatLogo()" style="filter: invert(1);align:center;vertical-align: middle;"/>\r
- </template>\r
- <span>{{ getFileFormatDesc() }}</span>\r
- </v-tooltip>\r
- <span v-if="!compact" class="body-2" style="vertical-align: middle;">{{ getFileFormatDesc() }}</span>\r
- </div>\r
-`,\r
- props: ['item','height','compact', 'dark', 'hiresonly'],\r
- data (){\r
- return{}\r
- },\r
- mounted() { },\r
- created() { },\r
- methods: { \r
-\r
- getFileFormatLogo() {\r
- if (this.item.quality == 0)\r
- return 'images/icons/mp3.png'\r
- else if (this.item.quality == 1)\r
- return 'images/icons/vorbis.png'\r
- else if (this.item.quality == 2)\r
- return 'images/icons/aac.png'\r
- else if (this.item.quality > 2)\r
- return 'images/icons/flac.png'\r
- },\r
- getFileFormatDesc() {\r
- var desc = '';\r
- if (this.item && this.item.quality)\r
- {\r
- if (this.item.quality == 0)\r
- desc = 'MP3';\r
- else if (this.item.quality == 1)\r
- desc = 'Ogg Vorbis';\r
- else if (this.item.quality == 2)\r
- desc = 'AAC';\r
- else if (this.item.quality > 2)\r
- desc = 'FLAC';\r
- else\r
- desc = 'unknown';\r
- }\r
- // append details\r
- if (this.item && this.item.metadata)\r
- {\r
- if (!!this.item.metadata && this.item.metadata.maximum_technical_specifications)\r
- desc += ' ' + this.item.metadata.maximum_technical_specifications;\r
- if (!!this.item.metadata && this.item.metadata.sample_rate && this.item.metadata.bit_depth)\r
- desc += ' ' + this.item.metadata.sample_rate + 'kHz ' + this.item.metadata.bit_depth + 'bit';\r
- }\r
- return desc;\r
- }\r
- }\r
- })\r
</v-list-tile-avatar>\r
<v-list-tile-content>\r
<v-list-tile-title>{{ players[player_id].name }}</v-list-tile-title>\r
- <v-list-tile-sub-title>{{ players[player_id].state }}</v-list-tile-sub-title>\r
+ <v-list-tile-sub-title>{{ $t('state.' + players[player_id].state) }}</v-list-tile-sub-title>\r
</v-list-tile-content>\r
</v-list-tile-action>\r
</v-list-tile>\r
prepend-icon="volume_down"\r
append-icon="volume_up"\r
@end="$emit('setPlayerVolume', child_id, $event)"\r
+ @click:append="$emit('setPlayerVolume', child_id, 'up')"\r
+ @click:prepend="$emit('setPlayerVolume', child_id, 'down')"\r
></v-slider>\r
</v-list-tile-sub-title>\r
</v-list-tile-content>\r
endpoint = "/playlists/"
item_id = item.item_id.toString();
var url = endpoint + item_id;
- router.push({ path: url, params: {provider: item.provider}});
+ console.log(url + ' - ' + item.provider);
+ router.push({ path: url, query: {provider: item.provider}});
}
String.prototype.formatDuration = function () {
<script src='./components/playmenu.vue.js'></script>
<script src='./components/volumecontrol.vue.js'></script>
<script src='./components/infoheader.vue.js'></script>
- <script src='./components/qualityicon.vue.js'></script>
+ <script src='./components/providericons.vue.js'></script>
<script src='./components/searchbox.vue.js'></script>
<script src='./strings.js'></script>
},
getInfo (lazy=true) {
this.$globals.loading = true;
- const api_url = '/api/artists/' + this.media_id
+ const api_url = '/api/artists/' + this.media_id;
+ console.log(api_url + ' - ' + this.provider);
axios
.get(api_url, { params: { lazy: lazy, provider: this.provider }})
.then(result => {
info: {},
items: [],
offset: 0,
+ active: 0
}
},
created() {
},
created() {
this.$globals.windowtitle = this.$t('queue')
- this.getQueueTracks();
- this.scroll(this.Queue);
+ this.getQueueTracks(0, 25);
},
methods: {
- getInfo () {
- const api_url = '/api/players/' + this.media_id
- axios
- .get(api_url, { params: { provider: this.provider }})
- .then(result => {
- data = result.data;
- this.info = data;
- })
- .catch(error => {
- console.log("error", error);
- });
- },
- getQueueTracks () {
- this.$globals.loading = true
+
+ getQueueTracks (offset, limit) {
const api_url = '/api/players/' + this.player_id + '/queue'
- axios
- .get(api_url, { params: { offset: this.offset, limit: 50}})
- .then(result => {
- data = result.data;
- this.items.push(...data);
- this.offset += 25;
- this.$globals.loading = false;
+ return axios.get(api_url, { params: { offset: offset, limit: limit}})
+ .then(response => {
+ if (response.data.length < 1 )
+ return;
+ this.items.push(...response.data)
+ return this.getQueueTracks(offset+limit, 100)
})
- .catch(error => {
- console.log("error", error);
- });
-
- },
- scroll (Browse) {
- window.onscroll = () => {
- let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;
- if (bottomOfWindow) {
- this.getQueueTracks();
- }
- };
}
}
})
}
},
created() {
- this.$globals.windowtitle = "Track info"
+ this.$globals.windowtitle = ""
this.getInfo();
},
methods: {
player_power_play: "Issue play command on power on",
file_prov_music_path: "Path to music files",
file_prov_playlists_path: "Path to playlists (.m3u)",
+ // player strings
+ players: "Players",
+ play_on: "Play on:",
+ play_now: "Play Now",
+ play_next: "Play Next",
+ add_queue: "Add to Queue",
+ show_info: "Show info",
+ state: {
+ playing: "playing",
+ stopped: "stopped",
+ paused: "paused",
+ off: "off"
+ }
},
player_power_play: "Automatisch afspelen bij inschakelen",
file_prov_music_path: "Pad naar muziek bestanden",
file_prov_playlists_path: "Pad naar playlist bestanden (.m3u)",
+ // player strings
+ players: "Spelers",
+ play_on: "Afspelen op:",
+ play_now: "Nu afspelen",
+ play_next: "Speel als volgende af",
+ add_queue: "Voeg toe aan wachtrij",
+ show_info: "Bekijk informatie",
+ state: {
+ playing: "afspelen",
+ stopped: "gestopt",
+ paused: "gepauzeerd",
+ off: "uitgeschakeld"
+ }
}
}
\ No newline at end of file