Newer
Older
Eliot Berriot
committed
<template>
<section v-if="currentTrack" class="player-wrapper ui bottom-player">
<div class="ui inverted segment fixed-controls" @click.prevent.stop="toggleMobilePlayer">
<div
:class="['ui', 'top attached', 'small', 'orange', 'inverted', {'indicating': isLoadingAudio}, 'progress']">
<div class="buffer bar" :data-percent="bufferProgress" :style="{ 'width': bufferProgress + '%' }"></div>
<div class="position bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div>
</div>
<div class="controls-row">
<div class="controls track-controls queue-not-focused desktop-and-up">
<div @click.stop.prevent="" class="ui tiny image" @click.stop.prevent="$router.push({name: 'library.tracks.detail', params: {id: currentTrack.id }})">
<img ref="cover" v-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.medium_square_crop)">
<img v-else src="../../assets/audio/default-cover.png">
Eliot Berriot
committed
</div>
<div @click.stop.prevent="" class="middle aligned content ellipsis">
<strong>
<router-link @click.stop.prevent="" class="small header discrete link track" :title="currentTrack.title" :to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}">
{{ currentTrack.title }}
</router-link>
</strong>
<div class="meta">
<router-link @click.stop.prevent="" class="discrete link" :title="currentTrack.artist.name" :to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}">
{{ currentTrack.artist.name }}</router-link><template v-if="currentTrack.album"> /<router-link @click.stop.prevent="" class="discrete link" :title="currentTrack.album.title" :to="{name: 'library.albums.detail', params: {id: currentTrack.album.id }}">
{{ currentTrack.album.title }}
</div>
Eliot Berriot
committed
</div>
</div>
<div class="controls track-controls queue-not-focused tablet-and-below">
<div class="ui tiny image">
<img ref="cover" v-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.medium_square_crop)">
<img v-else src="../../assets/audio/default-cover.png">
</div>
<div class="middle aligned content ellipsis">
<strong>
{{ currentTrack.title }}
</strong>
<div class="meta">
{{ currentTrack.artist.name }}<template v-if="currentTrack.album"> / {{ currentTrack.album.title }}</template>
</div>
</div>
<div class="controls desktop-and-up fluid align-right" v-if="$store.state.auth.authenticated">
<track-favorite-icon
class="control white"
:track="currentTrack"></track-favorite-icon>
<track-playlist-icon
class="control white"
:track="currentTrack"></track-playlist-icon>
<button
@click="$store.dispatch('moderation/hide', {type: 'artist', target: currentTrack.artist})"
:class="['ui', 'really', 'basic', 'circular', 'icon', 'button', 'control']"
:aria-label="labels.addArtistContentFilter"
:title="labels.addArtistContentFilter">
<i :class="['eye slash outline', 'basic', 'icon']"></i>
</button>
Eliot Berriot
committed
</div>
<div class="player-controls controls queue-not-focused">
<span
role="button"
:title="labels.previous"
:aria-label="labels.previous"
class="control tablet-and-up"
@click.prevent.stop="$store.dispatch('queue/previous')"
:disabled="!hasPrevious">
<i :class="['ui', 'large', {'disabled': !hasPrevious}, 'backward step', 'icon']" ></i>
</span>
<span
role="button"
v-if="!playing"
:title="labels.play"
:aria-label="labels.play"
@click.prevent.stop="togglePlay"
class="control">
<i :class="['ui', 'big', 'play', {'disabled': !currentTrack}, 'icon']"></i>
</span>
<span
role="button"
Eliot Berriot
committed
v-else
:title="labels.pause"
:aria-label="labels.pause"
@click.prevent.stop="togglePlay"
class="control">
<i :class="['ui', 'big', 'pause', {'disabled': !currentTrack}, 'icon']"></i>
</span>
<span
role="button"
:title="labels.next"
:aria-label="labels.next"
class="control"
@click.prevent.stop="$store.dispatch('queue/next')"
:disabled="!hasNext">
<i :class="['ui', 'large', {'disabled': !hasNext}, 'forward step', 'icon']" ></i>
</span>
</div>
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<div class="controls progress-controls queue-not-focused tablet-and-up small align-left">
<div class="timer">
<template v-if="!isLoadingAudio">
<span role="button" class="start" @click.stop.prevent="setCurrentTime(0)">{{currentTimeFormatted}}</span>
| <span class="total">{{durationFormatted}}</span>
</template>
<template v-else>
00:00 | 00:00
</template>
</div>
</div>
<div class="controls queue-controls when-queue-focused align-right">
<div class="group">
<volume-control class="expandable" />
<span
role="button"
v-if="looping === 0"
:title="labels.loopingDisabled"
:aria-label="labels.loopingDisabled"
@click.prevent.stop="$store.commit('player/looping', 1)"
:disabled="!currentTrack">
<i :class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'icon']"></i>
</span>
<span
role="button"
@click.prevent.stop="$store.commit('player/looping', 2)"
:title="labels.loopingSingle"
:aria-label="labels.loopingSingle"
v-if="looping === 1"
class="looping"
:disabled="!currentTrack">
<i
class="repeat icon">
<span class="ui circular tiny orange label">1</span>
</i>
</span>
<span
role="button"
:title="labels.loopingWhole"
:aria-label="labels.loopingWhole"
v-if="looping === 2"
:disabled="!currentTrack"
class="looping"
@click.prevent.stop="$store.commit('player/looping', 0)">
<i
class="repeat icon">
<span class="ui circular tiny orange label">∞</span>
</i>
</span>
<span
role="button"
:disabled="queue.tracks.length === 0"
:title="labels.shuffle"
:aria-label="labels.shuffle"
@click.prevent.stop="shuffle()">
<div v-if="isShuffling" class="ui inline shuffling inverted tiny active loader"></div>
<i v-else :class="['ui', 'random', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</span>
</div>
<div class="group">
<div class="fake-dropdown">
<span class="position control desktop-and-up" role="button" @click.stop="toggleMobilePlayer">
<i class="stream icon"></i>
<translate translate-context="Sidebar/Queue/Text" :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}">
%{ index } of %{ length }
</translate>
</span>
<span class="position control tablet-and-below" role="button" @click.stop="switchTab">
<i class="stream icon"></i>
<translate translate-context="Sidebar/Queue/Text" :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}">
%{ index } of %{ length }
</translate>
</span>
<span
class="control close-control desktop-and-up"
v-if="$store.state.ui.queueFocused"
@click.stop="toggleMobilePlayer">
<i class="large down angle icon"></i>
</span>
<span
class="control desktop-and-up"
v-else
@click.stop="toggleMobilePlayer">
<i class="large up angle icon"></i>
</span>
<span
class="control close-control tablet-and-below"
v-if="$store.state.ui.queueFocused === 'player'"
@click.stop="switchTab">
<i class="large up angle icon"></i>
</span>
<span
class="control tablet-and-below"
v-if="$store.state.ui.queueFocused === 'queue'"
@click.stop="switchTab">
<i class="large down angle icon"></i>
</span>
</div>
<span
class="control close-control tablet-and-below"
@click.stop="$store.commit('ui/queueFocused', null)">
<i class="x icon"></i>
</span>
</div>
</div>
Eliot Berriot
committed
</div>
</div>
<GlobalEvents
@keydown.space.prevent.exact="togglePlay"
@keydown.ctrl.shift.left.prevent.exact="previous"
@keydown.ctrl.shift.right.prevent.exact="next"
@keydown.shift.down.prevent.exact="$store.commit('player/incrementVolume', -0.1)"
@keydown.shift.up.prevent.exact="$store.commit('player/incrementVolume', 0.1)"
@keydown.right.prevent.exact="seek (5)"
@keydown.left.prevent.exact="seek (-5)"
@keydown.shift.right.prevent.exact="seek (30)"
@keydown.shift.left.prevent.exact="seek (-30)"
@keydown.m.prevent.exact="toggleMute"
@keydown.l.exact="$store.commit('player/toggleLooping')"
@keydown.s.exact="shuffle"
@keydown.f.exact="$store.dispatch('favorites/toggle', currentTrack.id)"
@keydown.q.exact="clean"
@keydown.e.exact="toggleMobilePlayer"
/>
</section>
Eliot Berriot
committed
</template>
<script>
import { mapState, mapGetters, mapActions } from "vuex"
import GlobalEvents from "@/components/utils/global-events"
import { Howl } from "howler"
import $ from 'jquery'
import _ from '@/lodash'
import url from '@/utils/url'
import axios from 'axios'
Eliot Berriot
committed
export default {
components: {
VolumeControl: () => import(/* webpackChunkName: "audio" */ "./VolumeControl"),
TrackFavoriteIcon: () => import(/* webpackChunkName: "auth-audio" */ "@/components/favorites/TrackFavoriteIcon"),
TrackPlaylistIcon: () => import(/* webpackChunkName: "auth-audio" */ "@/components/playlists/TrackPlaylistIcon"),
Eliot Berriot
committed
},
data() {
Eliot Berriot
committed
return {
sliderVolume: this.volume,
currentSound: null,
dummyAudio: null,
isUpdatingTime: false,
sourceErrors: 0,
progressInterval: null,
maxPreloaded: 3,
preloadDelay: 15,
listenDelay: 15,
listeningRecorded: null,
Eliot Berriot
committed
soundId: null,
playTimeout: null,
nextTrackPreloaded: false
Eliot Berriot
committed
}
},
mounted() {
this.$store.dispatch('player/updateProgress', 0)
this.$store.commit('player/playing', false)
this.$store.commit("player/isLoadingAudio", false)
Howler.unload() // clear existing cache, if any
this.nextTrackPreloaded = false
Eliot Berriot
committed
// we trigger the watcher explicitely it does not work otherwise
this.sliderVolume = this.volume
Eliot Berriot
committed
// this is needed to unlock audio playing under some browsers,
// cf https://github.com/goldfire/howler.js#mobilechrome-playback
// but we never actually load those audio files
this.dummyAudio = new Howl({
preload: false,
autoplay: false,
src: ["noop.webm", "noop.mp3"]
Eliot Berriot
committed
})
if (this.currentTrack) {
this.getSound(this.currentTrack)
}
// Add controls for notification drawer
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('play', this.togglePlay);
navigator.mediaSession.setActionHandler('pause', this.togglePlay);
navigator.mediaSession.setActionHandler('seekforward', this.seekForward);
navigator.mediaSession.setActionHandler('seekbackward', this.seekBackward);
navigator.mediaSession.setActionHandler('nexttrack', this.next);
navigator.mediaSession.setActionHandler('previoustrack', this.previous);
}
Eliot Berriot
committed
},
beforeDestroy () {
Eliot Berriot
committed
this.dummyAudio.unload()
Eliot Berriot
committed
},
destroyed() {
},
Eliot Berriot
committed
methods: {
togglePlay: "player/togglePlay",
mute: "player/mute",
unmute: "player/unmute",
clean: "queue/clean",
toggleMute: "player/toggleMute",
async getTrackData (trackData) {
let data = null
if (!trackData.uploads.length || trackData.uploads.length === 0) {
// we don't have upload informations for this track, we need to fetch it
await axios.get(`tracks/${trackData.id}/`).then((response) => {
data = response.data
}, error => {
data = null
})
} else {
return trackData
}
if (data === null) {
return
}
return data
},
shuffle() {
let disabled = this.queue.tracks.length === 0
if (this.isShuffling || disabled) {
return
}
let self = this
let msg = this.$pgettext('Content/Queue/Message', "Queue shuffled!")
this.isShuffling = true
setTimeout(() => {
self.$store.dispatch("queue/shuffle", () => {
self.$store.commit("ui/addMessage", {
date: new Date()
})
})
}, 100)
},
next() {
this.$store.dispatch("queue/next").then(() => {
self.$emit("next")
previous() {
this.$store.dispatch("queue/previous").then(() => {
self.$emit("previous")
handleError({ sound, error }) {
this.$store.commit("player/isLoadingAudio", false)
this.$store.dispatch("player/trackErrored")
},
getSound (trackData) {
let cached = this.getSoundFromCache(trackData)
if (cached) {
return cached.sound
}
let srcs = this.getSrcs(trackData)
let self = this
let sound = new Howl({
src: srcs.map((s) => { return s.url }),
format: srcs.map((s) => { return s.type }),
autoplay: false,
loop: false,
html5: true,
preload: true,
volume: this.volume,
onend: function () {
self.ended()
},
onunlock: function () {
Eliot Berriot
committed
if (self.$store.state.player.playing && self.sound) {
}
},
onload: function () {
let sound = this
let node = this._sounds[0]._node;
node.addEventListener('progress', () => {
if (sound != self.currentSound) {
return
}
self.updateBuffer(node)
})
},
onplay: function () {
self.$store.commit('player/isLoadingAudio', false)
self.$store.commit('player/resetErrorCount')
self.$store.commit('player/errored', false)
self.$store.commit('player/duration', this.duration())
},
onloaderror: function (sound, error) {
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
if (this != self.currentSound) {
return
}
console.log('Error while playing:', sound, error)
self.handleError({sound, error})
},
})
this.addSoundToCache(sound, trackData)
return sound
},
getSrcs: function (trackData) {
let sources = trackData.uploads.map(u => {
return {
type: u.extension,
url: this.$store.getters['instance/absoluteUrl'](u.listen_url),
}
})
// We always add a transcoded MP3 src at the end
// because transcoding is expensive, but we want browsers that do
// not support other codecs to be able to play it :)
sources.push({
type: 'mp3',
url: url.updateQueryString(
this.$store.getters['instance/absoluteUrl'](trackData.listen_url),
'to',
'mp3'
)
})
if (this.$store.state.auth.authenticated) {
// we need to send the token directly in url
// so authentication can be checked by the backend
// because for audio files we cannot use the regular Authentication
// header
sources.forEach(e => {
e.url = url.updateQueryString(e.url, 'jwt', this.$store.state.auth.token)
})
}
return sources
},
updateBuffer (node) {
// from https://github.com/goldfire/howler.js/issues/752#issuecomment-372083163
let range = 0;
let bf = node.buffered;
let time = node.currentTime;
try {
while(!(bf.start(range) <= time && time <= bf.end(range))) {
range += 1;
}
} catch (IndexSizeError) {
return
}
let loadPercentage
let start = bf.start(range)
let end = bf.end(range)
if (range === 0) {
// easy case, no user-seek
let loadStartPercentage = start / node.duration;
let loadEndPercentage = end / node.duration;
loadPercentage = loadEndPercentage - loadStartPercentage;
} else {
let loaded = end - start
let remainingToLoad = node.duration - start
// user seeked a specific position in the audio, our progress must be
// computed based on the remaining portion of the track
loadPercentage = loaded / remainingToLoad;
}
if (loadPercentage * 100 === this.bufferProgress) {
return
}
this.$store.commit('player/bufferProgress', loadPercentage * 100)
},
updateProgress: function () {
this.isUpdatingTime = true
if (this.currentSound && this.currentSound.state() === 'loaded') {
let t = this.currentSound.seek()
let d = this.currentSound.duration()
this.$store.dispatch('player/updateProgress', t)
this.updateBuffer(this.currentSound._sounds[0]._node)
let toPreload = this.$store.state.queue.tracks[this.currentIndex + 1]
if (!this.nextTrackPreloaded && toPreload && !this.getSoundFromCache(toPreload) && (t > this.preloadDelay || d - t < 30)) {
this.nextTrackPreloaded = true
if (t > this.listenDelay || d - t < 30) {
let onlyTrack = this.$store.state.queue.tracks.length === 1
if (this.listeningRecorded != this.currentTrack) {
this.listeningRecorded = this.currentTrack
this.$store.dispatch('player/trackListened', this.currentTrack)
}
}
seek (step) {
if (step > 0) {
// seek right
if (this.currentTime + step < this.duration) {
this.$store.dispatch('player/updateProgress', (this.currentTime + step))
} else {
this.next() // parenthesis where missing here
}
}
else {
// seek left
let position = Math.max(this.currentTime + step, 0)
this.$store.dispatch('player/updateProgress', position)
}
},
seekForward () {
this.seek (5)
},
seekBackward () {
this.seek (-5)
},
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
observeProgress: function (enable) {
let self = this
if (enable) {
if (self.progressInterval) {
clearInterval(self.progressInterval)
}
self.progressInterval = setInterval(() => {
self.updateProgress()
}, 1000)
} else {
clearInterval(self.progressInterval)
}
},
setCurrentTime (t) {
if (t < 0 | t > this.duration) {
return
}
if (!this.currentSound || !this.currentSound._sounds[0]) {
return
}
if (t === this.currentSound.seek()) {
return
}
if (t === 0) {
this.updateProgressThrottled.cancel()
}
this.currentSound.seek(t)
},
ended: function () {
let onlyTrack = this.$store.state.queue.tracks.length === 1
if (this.looping === 1 || (onlyTrack && this.looping === 2)) {
this.currentSound.seek(0)
this.$store.dispatch('player/updateProgress', 0)
this.soundId = this.currentSound.play(this.soundId)
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
} else {
this.$store.dispatch('player/trackEnded', this.currentTrack)
}
},
getSoundFromCache (trackData) {
return this.soundsCache.filter((d) => {
if (d.track.id !== trackData.id) {
return false
}
return true
})[0]
},
addSoundToCache (sound, trackData) {
let data = {
date: new Date(),
track: trackData,
sound: sound
}
this.soundsCache.push(data)
this.checkCache()
},
checkCache () {
let self = this
let toKeep = []
_.reverse(this.soundsCache).forEach((e) => {
if (toKeep.length < self.maxPreloaded) {
toKeep.push(e)
} else {
let src = e.sound._src
e.sound.unload()
}
})
this.soundsCache = _.reverse(toKeep)
},
removeFromCache (sound) {
let toKeep = []
this.soundsCache.forEach((e) => {
if (e.sound === sound) {
e.sound.unload()
} else {
toKeep.push(e)
}
})
this.soundsCache = toKeep
},
async loadSound (newValue, oldValue) {
let trackData = newValue
let oldSound = this.currentSound
if (oldSound && trackData !== oldValue) {
oldSound.stop(this.soundId)
this.soundId = null
}
if (!trackData) {
return
}
if (!this.isShuffling && trackData != oldValue) {
trackData = await this.getTrackData(trackData)
if (trackData === null) {
this.handleError({})
}
this.currentSound = this.getSound(trackData)
this.$store.commit('player/isLoadingAudio', true)
Eliot Berriot
committed
this.soundId = this.currentSound.play()
this.$store.commit('player/errored', false)
this.$store.commit('player/playing', true)
this.$store.dispatch('player/updateProgress', 0)
this.observeProgress(true)
},
toggleMobilePlayer () {
if (['queue', 'player'].indexOf(this.$store.state.ui.queueFocused) > -1) {
this.$store.commit('ui/queueFocused', null)
} else {
this.$store.commit('ui/queueFocused', 'player')
}
},
switchTab () {
if (this.$store.state.ui.queueFocused === 'player') {
this.$store.commit('ui/queueFocused', 'queue')
} else {
this.$store.commit('ui/queueFocused', 'player')
}
},
Eliot Berriot
committed
},
computed: {
...mapState({
currentIndex: state => state.queue.currentIndex,
playing: state => state.player.playing,
Eliot Berriot
committed
isLoadingAudio: state => state.player.isLoadingAudio,
volume: state => state.player.volume,
looping: state => state.player.looping,
duration: state => state.player.duration,
Eliot Berriot
committed
bufferProgress: state => state.player.bufferProgress,
Eliot Berriot
committed
errored: state => state.player.errored,
currentTime: state => state.player.currentTime,
queue: state => state.queue
}),
...mapGetters({
currentTrack: "queue/currentTrack",
hasNext: "queue/hasNext",
hasPrevious: "queue/hasPrevious",
emptyQueue: "queue/isEmpty",
durationFormatted: "player/durationFormatted",
currentTimeFormatted: "player/currentTimeFormatted",
progress: "player/progress"
}),
Eliot Berriot
committed
return _.throttle(this.updateProgress, 50)
labels() {
let audioPlayer = this.$pgettext('Sidebar/Player/Hidden text', "Media player")
let previousTrack = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Previous track")
let play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Play track")
let pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Pause track")
let next = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Next track")
let unmute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Unmute")
let mute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Mute")
let expandQueue = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Expand queue")
let loopingDisabled = this.$pgettext('Sidebar/Player/Icon.Tooltip',
"Looping disabled. Click to switch to single-track looping."
)
let loopingSingle = this.$pgettext('Sidebar/Player/Icon.Tooltip',
"Looping on a single track. Click to switch to whole queue looping."
)
let loopingWhole = this.$pgettext('Sidebar/Player/Icon.Tooltip',
"Looping on whole queue. Click to disable looping."
)
let shuffle = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Shuffle your queue")
let clear = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Clear your queue")
let addArtistContentFilter = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…')
audioPlayer,
previousTrack,
play,
pause,
next,
unmute,
mute,
loopingDisabled,
loopingSingle,
loopingWhole,
shuffle,
expandQueue,
Eliot Berriot
committed
},
watch: {
currentTrack: {
async handler (newValue, oldValue) {
if (newValue === oldValue) {
return
}
this.nextTrackPreloaded = false
Eliot Berriot
committed
clearTimeout(this.playTimeout)
let self = this
if (this.currentSound) {
this.currentSound.pause()
Eliot Berriot
committed
this.$store.commit("player/isLoadingAudio", true)
this.playTimeout = setTimeout(async () => {
await self.loadSound(newValue, oldValue)
}, 500);
// If the session is playing as a PWA, populate the notification
// with details from the track
if ('mediaSession' in navigator) {
let metatata = {
title: this.currentTrack.title,
artist: this.currentTrack.artist.name,
}
if (this.currentTrack.album) {
metadata.album = this.currentTrack.album.title
metadata.artwork = [
{ src: this.currentTrack.album.cover.original, sizes: '96x96', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '128x128', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '192x192', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '256x256', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '384x384', type: 'image/png' },
{ src: this.currentTrack.album.cover.original, sizes: '512x512', type: 'image/png' },
}
navigator.mediaSession.metadata = new MediaMetadata(metadata);
},
volume(newValue) {
Eliot Berriot
committed
this.sliderVolume = newValue
if (this.currentSound) {
this.currentSound.volume(newValue)
}
Eliot Berriot
committed
},
sliderVolume(newValue) {
this.$store.commit("player/volume", newValue)
},
playing: async function (newValue) {
if (this.currentSound) {
if (newValue === true) {
this.soundId = this.currentSound.play(this.soundId)
}
} else {
await this.loadSound(this.currentTrack, null)
}
this.observeProgress(newValue)
},
currentTime (newValue) {
if (!this.isUpdatingTime) {
this.setCurrentTime(newValue)
}
this.isUpdatingTime = false
},
emptyQueue (newValue) {
if (newValue) {
Howler.unload()
}
Eliot Berriot
committed
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import "../../style/vendor/media";
.controls {
display: flex;
justify-content: space-between;
Eliot Berriot
committed
}
Eliot Berriot
committed
.controls .icon.big {
cursor: pointer;
font-size: 2em !important;
}
.controls .icon {
cursor: pointer;
vertical-align: middle;
}
.timer {
font-size: 1.2em;
.ui.circular.label {
font-family: sans-serif;
font-size: 0.5em !important;
padding: 2px 0 !important;
width: 15px !important;
height: 15px !important;
min-width: 15px !important;
min-height: 15px !important;
@include media(">desktop") {
font-size: 0.6em !important;
}
Eliot Berriot
committed
}
}
.shuffling.loader.inline {
margin: 0;
}
.control.circular.button {
padding: 0;
border: none;
background-color: transparent;
color: inherit;
&:focus {
box-shadow: none;
}
Eliot Berriot
committed
}
.fake-dropdown {
border: 1px solid gray;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: space-between;
min-width: 10em;
Eliot Berriot
committed
> * {
padding: 0.5em;
}
.position.control {
Eliot Berriot
committed
padding-right: 1em;
flex-grow: 1;
}
.angle.icon {
margin-right: 0;
}
Eliot Berriot
committed
}