work on player

This commit is contained in:
bkfox 2020-11-07 17:26:08 +01:00
parent a0a1c92cfe
commit 222300945e
12 changed files with 323 additions and 264 deletions

View File

@ -7411,10 +7411,20 @@ a.navbar-item.is-active {
.player { .player {
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6); } box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6); }
.player .player-panels {
height: 0%;
transition: height 3s; }
.player .player-panels.is-open {
height: auto; }
.player .player-panel { .player .player-panel {
margin: 0.4em; margin: 0.4em;
max-height: 80%; max-height: 80%;
overflow-y: auto; } overflow-y: auto; }
.player .progress {
margin: 0em;
padding: 0em;
border-color: #209cee;
border-style: 'solid'; }
.player .player-bar { .player .player-bar {
border-top: 1px #b5b5b5 solid; } border-top: 1px #b5b5b5 solid; }
.player .player-bar > .media-left:not(:last-child) { .player .player-bar > .media-left:not(:last-child) {
@ -7431,7 +7441,10 @@ a.navbar-item.is-active {
font-size: 1.5rem !important; font-size: 1.5rem !important;
height: 2.5em; height: 2.5em;
min-width: 2.5em; min-width: 2.5em;
border-radius: 0px; } border-radius: 0px;
transition: background-color 1s; }
.player .player-bar .button:focus {
background-color: #209cee; }
.player .player-bar .title { .player .player-bar .title {
margin: 0em; } margin: 0em; }

File diff suppressed because one or more lines are too long

View File

@ -7390,10 +7390,20 @@ a.navbar-item.is-active {
.player { .player {
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6); } box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6); }
.player .player-panels {
height: 0%;
transition: height 3s; }
.player .player-panels.is-open {
height: auto; }
.player .player-panel { .player .player-panel {
margin: 0.4em; margin: 0.4em;
max-height: 80%; max-height: 80%;
overflow-y: auto; } overflow-y: auto; }
.player .progress {
margin: 0em;
padding: 0em;
border-color: #209cee;
border-style: 'solid'; }
.player .player-bar { .player .player-bar {
border-top: 1px #b5b5b5 solid; } border-top: 1px #b5b5b5 solid; }
.player .player-bar > .media-left:not(:last-child) { .player .player-bar > .media-left:not(:last-child) {
@ -7410,7 +7420,10 @@ a.navbar-item.is-active {
font-size: 1.5rem !important; font-size: 1.5rem !important;
height: 2.5em; height: 2.5em;
min-width: 2.5em; min-width: 2.5em;
border-radius: 0px; } border-radius: 0px;
transition: background-color 1s; }
.player .player-bar .button:focus {
background-color: #209cee; }
.player .player-bar .title { .player .player-bar .title {
margin: 0em; } margin: 0em; }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -39,11 +39,10 @@ export default {
methods: { methods: {
get(index) { return this.set.get(index) }, get(index) { return this.set.get(index) },
find(item) { return this.set.find(item) }, find(pred) { return this.set.find(pred) },
findIndex(item) { return this.set.findIndex(item) }, findIndex(pred) { return this.set.findIndex(pred) },
push(...items) { push(...items) {
let index = this.set.length;
for(var item of items) for(var item of items)
this.set.push(item); this.set.push(item);
}, },
@ -62,7 +61,10 @@ export default {
return this.selectedIndex; return this.selectedIndex;
}, },
unselect() {
this.$emit('unselect', { item: this.selected, index: this.selectedIndex});
this.selectedIndex = -1;
},
}, },
} }
</script> </script>

View File

@ -119,7 +119,6 @@ export class Set {
} }
get length() { return this.items.length } get length() { return this.items.length }
get(index) { return this.items[index] }
/** /**
* Fetch multiple items from server * Fetch multiple items from server
@ -156,17 +155,24 @@ export class Set {
} }
/** /**
* Find item by id * Get item at index
*/ */
find(item) { get(index) { return this.items[index] }
return this.items.find(x => x.id == item.id);
/**
* Find an item by id or using a predicate function
*/
find(pred) {
return pred instanceof Function ? this.items.find(pred)
: this.items.find(x => x.id == pred.id);
} }
/** /**
* Find item index by id * Find item index by id or using a predicate function
*/ */
findIndex(item) { findIndex(pred) {
return this.items.findIndex(x => x.id == item.id); return pred instanceof Function ? this.items.findIndex(pred)
: this.items.findIndex(x => x.id == pred.id);
} }
/** /**
@ -177,7 +183,7 @@ export class Set {
if(this.unique) { if(this.unique) {
let index = this.findIndex(item); let index = this.findIndex(item);
if(index > -1) if(index > -1)
this.items.splice(index,1); return;
} }
if(this.max && this.items.length >= this.max) if(this.max && this.items.length >= this.max)
this.items.splice(0,this.items.length-this.max) this.items.splice(0,this.items.length-this.max)

View File

@ -1,16 +1,6 @@
<template> <template>
<div class="player"> <div class="player">
<Playlist ref="history" class="player-panel menu" v-show="panel == 'history'" <div :class="['player-panels', panel ? 'is-open' : '']">
name="History"
:editable="true" :player="self" :set="sets.history" @select="play('pin', $event.index)"
listClass="menu-list" itemClass="menu-item">
<template v-slot:header="">
<p class="menu-label">
<span class="icon"><span class="fa fa-clock"></span></span>
History
</p>
</template>
</Playlist>
<Playlist ref="pin" class="player-panel menu" v-show="panel == 'pin'" <Playlist ref="pin" class="player-panel menu" v-show="panel == 'pin'"
name="Pinned" name="Pinned"
:editable="true" :player="self" :set="sets.pin" @select="play('pin', $event.index)" :editable="true" :player="self" :set="sets.pin" @select="play('pin', $event.index)"
@ -32,6 +22,7 @@
</p> </p>
</template> </template>
</Playlist> </Playlist>
</div>
<div class="player-bar media"> <div class="player-bar media">
<div class="media-left"> <div class="media-left">
@ -47,24 +38,18 @@
</div> </div>
<div class="media-content"> <div class="media-content">
<slot name="content" :current="current"></slot> <slot name="content" :current="current"></slot>
<Progress v-if="this.duration" :value="this.currentTime" :max="this.duration" <Progress v-if="loaded && duration" :value="currentTime" :max="this.duration"
:format="displayTime" :format="displayTime"
@select="audio.currentTime = $event"></Progress> @select="audio.currentTime = $event"></Progress>
</div> </div>
<div class="media-right"> <div class="media-right">
<button class="button has-text-weight-bold" v-if="loaded" @click="playLive()"> <button class="button has-text-weight-bold" v-if="loaded" @click="playLive()">
<span class="icon has-text-danger"> <span class="icon is-size-6 has-text-danger">
<span class="fa fa-broadcast-tower"></span> <span class="fa fa-circle"></span>
</span> </span>
<span>Live</span> <span>Live</span>
</button> </button>
<button :class="playlistButtonClass('history')" <button ref="pinPlaylistButton" :class="playlistButtonClass('pin')"
@click="togglePanel('history')">
<span class="mr-2 is-size-6" v-if="sets.history.length">
{{ sets.history.length }}</span>
<span class="icon"><span class="fa fa-clock"></span></span>
</button>
<button :class="playlistButtonClass('pin')"
@click="togglePanel('pin')"> @click="togglePanel('pin')">
<span class="mr-2 is-size-6" v-if="sets.pin.length"> <span class="mr-2 is-size-6" v-if="sets.pin.length">
{{ sets.pin.length }}</span> {{ sets.pin.length }}</span>
@ -86,7 +71,7 @@
import Vue, { ref } from 'vue'; import Vue, { ref } from 'vue';
import Live from './live'; import Live from './live';
import Playlist from './playlist'; import Playlist from './playlist';
import Progress from './playerProgress'; import Progress from './progress';
import Sound from './sound'; import Sound from './sound';
import {Set} from './model'; import {Set} from './model';
@ -111,7 +96,7 @@ export default {
}); });
return { return {
audio, duration: 0, currentTime: 0, audio, duration: 0, currentTime: 0, state: State.paused,
/// Live instance /// Live instance
live: this.liveArgs ? new Live(this.liveArgs) : null, live: this.liveArgs ? new Live(this.liveArgs) : null,
@ -125,7 +110,6 @@ export default {
sets: { sets: {
queue: Set.storeLoad(Sound, "playlist.queue", { max: 30, unique: true }), queue: Set.storeLoad(Sound, "playlist.queue", { max: 30, unique: true }),
pin: Set.storeLoad(Sound, "player.pin", { max: 30, unique: true }), pin: Set.storeLoad(Sound, "player.pin", { max: 30, unique: true }),
history: Set.storeLoad(Sound, "player.history", { max: 30, unique: true }),
} }
} }
}, },
@ -159,7 +143,11 @@ export default {
seconds = (seconds - s) / 60; seconds = (seconds - s) / 60;
let m = seconds % 60; let m = seconds % 60;
let h = (seconds - m) / 60; let h = (seconds - m) / 60;
return h ? `${h}:${m}:${s}` : `${m}:${s}`;
let [ss,mm,hh] = [s.toString().padStart(2, '0'),
m.toString().padStart(2, '0'),
h.toString().padStart(2, '0')];
return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;
}, },
playlistButtonClass(name) { playlistButtonClass(name) {
@ -172,24 +160,23 @@ export default {
}, },
/// Show/hide panel /// Show/hide panel
togglePanel(panel) { togglePanel(panel) { this.panel = this.panel == panel ? null : panel },
this.panel = this.panel == panel ? null : panel;
},
/// Return True if item is loaded /// Return True if item is loaded
isLoaded(item) { isLoaded(item) { return this.loaded && this.loaded.id == item.id },
return this.loaded && this.loaded.src == item.src;
},
/// Return True if item is loaded /// Return True if item is loaded
isPlaying(item) { isPlaying(item) { return this.isLoaded(item) && !this.paused },
return this.isLoaded(item) && !this.player.paused;
_setPlaylist(playlist) {
this.playlist = playlist ? this.$refs[playlist] : null;
for(var p in this.sets)
if(p != playlist)
this.$refs[p].unselect();
}, },
load(playlist, {src=null, item=null}={}) { load(playlist, {src=null, item=null}={}) {
src = src || item.src; src = src || item.src;
this.loaded = item; this.loaded = item;
this.playlist = playlist ? this.$refs[playlist] : null; this._setPlaylist(playlist);
const audio = this.audio; const audio = this.audio;
if(src instanceof Array) { if(src instanceof Array) {
@ -217,8 +204,7 @@ export default {
console.log('play', playlist, index, this.audio); console.log('play', playlist, index, this.audio);
let item = this.$refs[playlist].get(index); let item = this.$refs[playlist].get(index);
if(item) { if(item) {
this.load(playlist, {item: item}); this.load(playlist, {item});
this.sets.history.push(item);
this.audio.play().catch(e => console.error(e)) this.audio.play().catch(e => console.error(e))
} }
else else
@ -266,8 +252,7 @@ export default {
this.sets.pin.remove(index); this.sets.pin.remove(index);
else { else {
this.sets.pin.push(item); this.sets.pin.push(item);
if(!this.panel) this.$refs.pinPlaylistButton.focus();
this.panel = 'pin';
} }
}, },

View File

@ -1,46 +0,0 @@
<template>
<div class="media">
<div class="media-left">
<slot name="value" :value="value" :max="max">{{ format(value) }}</slot>
</div>
<div ref="bar" class="media-content" @click.stop="onClick">
<div :class="progressClass" :style="progressStyle">&nbsp;</div>
</div>
<div class="media-right">
<slot name="value" :value="value" :max="max">{{ format(max) }}</slot>
</div>
</div>
</template>
<script>
export default {
props: {
value: Number,
max: Number,
format: { type: Function, default: x => x },
progressClass: { default: 'has-background-primary' },
vertical: { type: Boolean, default: false },
},
computed: {
progressStyle() {
if(!this.max)
return null;
return this.vertical ? { height: (this.max ? this.value * 100 / this.max : 0) + '%' }
: { width: (this.max ? this.value * 100 / this.max : 0) + '%' };
},
},
methods: {
xToValue(x) { return x * this.max / this.$refs.bar.getBoundingClientRect().width },
yToValue(y) { return y * this.max / this.$refs.bar.getBoundingClientRect().height },
onClick(event) {
let rect = event.currentTarget.getBoundingClientRect()
this.$emit('select', this.vertical ? this.yToValue(event.clientY - rect.y)
: this.xToValue(event.clientX - rect.x));
},
},
}
</script>

View File

@ -0,0 +1,67 @@
<template>
<div class="media">
<div class="media-left">
<slot name="value" :value="valueDisplay" :max="max">{{ format(valueDisplay) }}</slot>
</div>
<div ref="bar" class="media-content" @click.stop="onClick" @mouseleave.stop="onMouseMove"
@mousemove.stop="onMouseMove">
<div :class="progressClass" :style="progressStyle">&nbsp;</div>
</div>
<div class="media-right">
<slot name="value" :value="valueDisplay" :max="max">{{ format(max) }}</slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
hoverValue: null,
}
},
props: {
value: Number,
max: Number,
format: { type: Function, default: x => x },
progressClass: { default: 'has-background-primary' },
vertical: { type: Boolean, default: false },
},
computed: {
valueDisplay() { return this.hoverValue === null ? this.value : this.hoverValue; },
progressStyle() {
if(!this.max)
return null;
let value = this.max ? this.valueDisplay * 100 / this.max : 0;
return this.vertical ? { height: `${value}%` } : { width: `${value}%` };
},
},
methods: {
xToValue(x) { return x * this.max / this.$refs.bar.getBoundingClientRect().width },
yToValue(y) { return y * this.max / this.$refs.bar.getBoundingClientRect().height },
valueFromEvent(event) {
let rect = event.currentTarget.getBoundingClientRect()
return this.vertical ? this.yToValue(event.clientY - rect.y)
: this.xToValue(event.clientX - rect.x);
},
onClick(event) {
this.$emit('select', this.valueFromEvent(event));
},
onMouseMove(event) {
if(event.type == 'mouseleave')
this.hoverValue = null;
else {
this.hoverValue = this.valueFromEvent(event);
}
},
},
}
</script>

View File

@ -3,7 +3,7 @@
<div class="media-left" v-if="hasAction('play')"> <div class="media-left" v-if="hasAction('play')">
<button class="button" @click="$emit('togglePlay')"> <button class="button" @click="$emit('togglePlay')">
<div class="icon"> <div class="icon">
<span class="fa fa-pause" v-if="playing || loading"></span> <span class="fa fa-pause" v-if="playing"></span>
<span class="fa fa-play" v-else></span> <span class="fa fa-play" v-else></span>
</div> </div>
</button> </button>
@ -16,7 +16,7 @@
</slot> </slot>
</div> </div>
<div class="media-right"> <div class="media-right">
<button class="button" v-if="player.$refs.pin != $parent" @click.stop="player.togglePin(item)"> <button class="button" v-if="player.sets.pin != $parent.set" @click.stop="player.togglePin(item)">
<span class="icon is-small"> <span class="icon is-small">
<span :class="(pinned ? '' : 'has-text-grey-light ') + 'fa fa-thumbtack'"></span> <span :class="(pinned ? '' : 'has-text-grey-light ') + 'fa fa-thumbtack'"></span>
</span> </span>
@ -43,9 +43,8 @@ export default {
computed: { computed: {
item() { return this.data instanceof Model ? this.data : new Sound(this.data || {}); }, item() { return this.data instanceof Model ? this.data : new Sound(this.data || {}); },
loaded() { return this.player && this.player.isLoaded(this.item) }, loaded() { return this.player && this.player.isLoaded(this.item) },
playing() { return this.player && this.player.playing && this.loaded }, playing() { return this.player && this.player.isPlaying(this.item) },
paused() { return this.player && this.player.paused && this.loaded }, paused() { return this.player && this.player.paused && this.loaded },
loading() { return this.player && this.player.loading && this.loaded },
pinned() { return this.player && this.player.sets.pin.find(this.item) }, pinned() { return this.player && this.player.sets.pin.find(this.item) },
}, },

View File

@ -172,12 +172,27 @@ a.navbar-item.is-active {
.player { .player {
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6); box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
.player-panels {
height: 0%;
transition: height 3s;
}
.player-panels.is-open {
height: auto;
}
.player-panel { .player-panel {
margin: 0.4em; margin: 0.4em;
max-height: 80%; max-height: 80%;
overflow-y: auto; overflow-y: auto;
} }
.progress {
margin: 0em;
padding: 0em;
border-color: $info;
border-style: 'solid';
}
.player-bar { .player-bar {
border-top: 1px $grey-light solid; border-top: 1px $grey-light solid;
@ -204,6 +219,11 @@ a.navbar-item.is-active {
height: 2.5em; height: 2.5em;
min-width: 2.5em; min-width: 2.5em;
border-radius: 0px; border-radius: 0px;
transition: background-color 1s;
}
.button:focus {
background-color: $info;
} }
.title { .title {