Rewrite player
This commit is contained in:
parent
32cf9642c6
commit
16687ee669
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ul :style="{marginBottom: currentEpisode ? '6rem' : 'auto'}">
|
||||
<ul :style="{marginBottom: episode ? '6rem' : 'auto'}">
|
||||
<NcListItem v-for="feed in feeds"
|
||||
:key="feed.link"
|
||||
:details="formatTimeAgo(new Date(feed.fetchedAtUnix*1000))"
|
||||
@ -43,7 +43,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentEpisode() {
|
||||
episode() {
|
||||
return this.$store.state.player.episode
|
||||
},
|
||||
},
|
||||
|
@ -17,7 +17,7 @@
|
||||
{{ formatTimer(new Date(episode.episodeDuration*1000)) }}
|
||||
</template>
|
||||
<template #actions>
|
||||
<NcActionButton @click="isCurrentEpisode(episode) ? play(null) : play(episode)">
|
||||
<NcActionButton @click="isCurrentEpisode(episode) ? load(null) : load(episode)">
|
||||
<template #icon>
|
||||
<PlayButton v-if="!isCurrentEpisode(episode)" :size="20" />
|
||||
<StopButton v-if="isCurrentEpisode(episode)" :size="20" />
|
||||
@ -81,8 +81,8 @@ export default {
|
||||
isCurrentEpisode(episode) {
|
||||
return this.currentEpisode && this.currentEpisode.episodeUrl === episode.episodeUrl
|
||||
},
|
||||
play(episode) {
|
||||
this.$store.commit('player/play', episode)
|
||||
load(episode) {
|
||||
this.$store.commit('player/load', episode)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,29 +1,13 @@
|
||||
<template>
|
||||
<div v-if="episode" class="footer">
|
||||
<audio id="audio-player"
|
||||
autoplay
|
||||
preload
|
||||
:src="episode.episodeUrl"
|
||||
@durationchange="duration = audio.duration"
|
||||
@ended="stop"
|
||||
@loadeddata="loadeddata"
|
||||
@pause="pause"
|
||||
@play="paused = false"
|
||||
@seeked="currentTime = audio.currentTime"
|
||||
@timeupdate="currentTime = audio.currentTime"
|
||||
@volumechange="volume = audio.volume" />
|
||||
<NcLoadingIcon v-if="loading" />
|
||||
<ProgressBar v-if="!loading"
|
||||
:current-time="currentTime"
|
||||
:duration="duration" />
|
||||
<div v-if="!loading" class="player">
|
||||
<img :src="episode.episodeImage">
|
||||
<div v-if="player.episode" class="footer">
|
||||
<NcLoadingIcon v-if="!player.loaded" />
|
||||
<ProgressBar v-if="player.loaded" />
|
||||
<div v-if="player.loaded" class="player">
|
||||
<img :src="player.episode.episodeImage">
|
||||
<Infos />
|
||||
<Controls :paused="paused" @stop="stop" />
|
||||
<Timer class="timer"
|
||||
:current-time="currentTime"
|
||||
:duration="duration" />
|
||||
<Volume class="volume" :volume="volume" />
|
||||
<Controls />
|
||||
<Timer class="timer" />
|
||||
<Volume class="volume" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -35,10 +19,6 @@ import { NcLoadingIcon } from '@nextcloud/vue'
|
||||
import ProgressBar from './ProgressBar.vue'
|
||||
import Timer from './Timer.vue'
|
||||
import Volume from './Volume.vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { formatEpisodeTimestamp } from '../../utils/time.js'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
||||
export default {
|
||||
name: 'Bar',
|
||||
@ -50,56 +30,9 @@ export default {
|
||||
Timer,
|
||||
Volume,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTime: 0,
|
||||
duration: 0,
|
||||
loading: true,
|
||||
paused: false,
|
||||
url: atob(this.$route.params.url),
|
||||
volume: 1,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
audio() {
|
||||
return document.getElementById('audio-player')
|
||||
},
|
||||
episode() {
|
||||
return this.$store.state.player.episode
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadeddata() {
|
||||
this.loading = false
|
||||
|
||||
if (this.episode.episodeAction) {
|
||||
this.audio.currentTime = this.episode.episodeAction.position
|
||||
}
|
||||
},
|
||||
pause() {
|
||||
this.paused = true
|
||||
this.track()
|
||||
},
|
||||
stop() {
|
||||
this.pause()
|
||||
this.$store.commit('player/play', null)
|
||||
},
|
||||
async track() {
|
||||
try {
|
||||
await axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [{
|
||||
podcast: this.url,
|
||||
episode: this.episode.episodeUrl,
|
||||
guid: this.episode.episodeGuid,
|
||||
action: 'play',
|
||||
timestamp: formatEpisodeTimestamp(new Date()),
|
||||
started: Math.round(this.episode.episodeAction ? this.episode.episodeAction.started : 0),
|
||||
position: Math.round(this.audio.currentTime),
|
||||
total: Math.round(this.audio.duration),
|
||||
}])
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
showError(t('Error while saving position on API'))
|
||||
}
|
||||
player() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="controls">
|
||||
<PauseButton v-if="!paused"
|
||||
<PauseButton v-if="!player.paused"
|
||||
class="pointer"
|
||||
:size="50"
|
||||
@click="() => audio.pause()" />
|
||||
<PlayButton v-if="paused"
|
||||
@click="$store.dispatch('player/pause')" />
|
||||
<PlayButton v-if="player.paused"
|
||||
class="pointer"
|
||||
:size="50"
|
||||
@click="() => audio.play()" />
|
||||
@click="$store.dispatch('player/play')" />
|
||||
<StopButton class="pointer"
|
||||
:size="30"
|
||||
@click="$emit('stop')" />
|
||||
@click="$store.dispatch('player/stop')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -26,15 +26,9 @@ export default {
|
||||
PlayButton,
|
||||
StopButton,
|
||||
},
|
||||
props: {
|
||||
paused: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
audio() {
|
||||
return document.getElementById('audio-player')
|
||||
player() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<a :href="episode.episodeLink" target="_blank">
|
||||
<strong>{{ episode.episodeName }}</strong>
|
||||
<a :href="player.episode.episodeLink" target="_blank">
|
||||
<strong>{{ player.episode.episodeName }}</strong>
|
||||
</a>
|
||||
<router-link :to="podcastUrl">
|
||||
<i>{{ episode.podcastName }}</i>
|
||||
<router-link :to="player.podcastUrl">
|
||||
<i>{{ player.episode.podcastName }}</i>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
@ -12,14 +12,9 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'Infos',
|
||||
data() {
|
||||
return {
|
||||
podcastUrl: atob(this.$route.params.url),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
episode() {
|
||||
return this.$store.state.player.episode
|
||||
player() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div @click="(event) => audio.currentTime = event.x * duration / event.target.offsetWidth">
|
||||
<NcProgressBar size="medium" :value="currentTime * 100 / duration" />
|
||||
<div @click="(event) => $store.dispatch('player/seek', event.x * player.duration / event.target.offsetWidth)">
|
||||
<NcProgressBar size="medium" :value="player.currentTime * 100 / player.duration" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -12,19 +12,9 @@ export default {
|
||||
components: {
|
||||
NcProgressBar,
|
||||
},
|
||||
props: {
|
||||
currentTime: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
audio() {
|
||||
return document.getElementById('audio-player')
|
||||
player() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<span>{{ formatTimer(new Date(currentTime*1000)) }}</span>
|
||||
<span>{{ formatTimer(new Date(player.currentTime*1000)) }}</span>
|
||||
<span>/</span>
|
||||
<span>{{ formatTimer(new Date(duration*1000)) }}</span>
|
||||
<span>{{ formatTimer(new Date(player.duration*1000)) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -11,14 +11,9 @@ import { formatTimer } from '../../utils/time.js'
|
||||
|
||||
export default {
|
||||
name: 'Timer',
|
||||
props: {
|
||||
currentTime: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
required: true,
|
||||
computed: {
|
||||
player() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<VolumeHigh v-if="volume > 0.7"
|
||||
<VolumeHigh v-if="player.volume > 0.7"
|
||||
class="pointer"
|
||||
:size="30"
|
||||
@click="mute" />
|
||||
<VolumeLow v-if="volume > 0 && volume <= 0.3"
|
||||
<VolumeLow v-if="player.volume > 0 && player.volume <= 0.3"
|
||||
class="pointer"
|
||||
:size="30"
|
||||
@click="mute" />
|
||||
<VolumeMedium v-if="volume > 0.3 && volume <= 0.7"
|
||||
<VolumeMedium v-if="player.volume > 0.3 && player.volume <= 0.7"
|
||||
class="pointer"
|
||||
:size="30"
|
||||
@click="mute" />
|
||||
<VolumeMute v-if="volume == 0"
|
||||
<VolumeMute v-if="player.volume == 0"
|
||||
class="pointer"
|
||||
:size="30"
|
||||
@click="unmute" />
|
||||
@ -20,8 +20,8 @@
|
||||
min="0"
|
||||
step="0.1"
|
||||
type="range"
|
||||
:value="volume"
|
||||
@change="(event) => audio.volume = event.target.value">
|
||||
:value="player.volume"
|
||||
@change="(event) => $store.dispatch('player/volume', event.target.value)">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -39,29 +39,23 @@ export default {
|
||||
VolumeMedium,
|
||||
VolumeMute,
|
||||
},
|
||||
props: {
|
||||
volume: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
volumeMuted: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
audio() {
|
||||
return document.getElementById('audio-player')
|
||||
player() {
|
||||
return this.$store.state.player
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
mute() {
|
||||
this.volumeMuted = this.audio.volume
|
||||
this.audio.volume = 0
|
||||
this.volumeMuted = this.player.volume
|
||||
this.$store.dispatch('player/volume', 0)
|
||||
},
|
||||
unmute() {
|
||||
this.audio.volume = this.volumeMuted
|
||||
this.$store.dispatch('player/volume', this.volumeMuted)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
</NcAppNavigationNew>
|
||||
</router-link>
|
||||
<NcLoadingIcon v-if="loading" />
|
||||
<ul v-if="!loading" :style="{marginBottom: currentEpisode ? '6rem' : 'auto'}">
|
||||
<ul v-if="!loading" :style="{marginBottom: episode ? '6rem' : 'auto'}">
|
||||
<Item v-for="subscriptionUrl of subscriptions"
|
||||
:key="subscriptionUrl"
|
||||
:url="subscriptionUrl" />
|
||||
@ -45,7 +45,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentEpisode() {
|
||||
episode() {
|
||||
return this.$store.state.player.episode
|
||||
},
|
||||
subscriptions() {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import Vuex, { Store } from 'vuex'
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
import App from './App.vue'
|
||||
import Vue from 'vue'
|
||||
import { generateFilePath } from '@nextcloud/router'
|
||||
import modules from './modules/index.js'
|
||||
import router from './router.js'
|
||||
import store from './store/main.js'
|
||||
|
||||
// eslint-disable-next-line
|
||||
__webpack_public_path__ = generateFilePath(appName, '', 'js/')
|
||||
@ -13,12 +12,9 @@ const t = (...args) => translate('repod', ...args)
|
||||
const n = (...args) => translatePlural('repod', ...args)
|
||||
|
||||
Vue.mixin({ methods: { t, n } })
|
||||
Vue.use(Vuex)
|
||||
|
||||
Vue.prototype.AppConfig = window.oc_appconfig
|
||||
|
||||
const store = new Store({ modules })
|
||||
|
||||
export default new Vue({
|
||||
el: '#content',
|
||||
router,
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { player } from './player.js'
|
||||
import { subscriptions } from './subscriptions.js'
|
||||
|
||||
export default {
|
||||
player,
|
||||
subscriptions,
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
export const player = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
episode: null,
|
||||
},
|
||||
mutations: {
|
||||
play: (state, episode) => {
|
||||
state.episode = episode
|
||||
},
|
||||
},
|
||||
}
|
15
src/store/main.js
Normal file
15
src/store/main.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Vuex, { Store } from 'vuex'
|
||||
import Vue from 'vue'
|
||||
import { player } from './player.js'
|
||||
import { subscriptions } from './subscriptions.js'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Store({
|
||||
modules: {
|
||||
player,
|
||||
subscriptions,
|
||||
},
|
||||
})
|
||||
|
||||
export default store
|
85
src/store/player.js
Normal file
85
src/store/player.js
Normal file
@ -0,0 +1,85 @@
|
||||
import axios from '@nextcloud/axios'
|
||||
import { formatEpisodeTimestamp } from '../utils/time.js'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import router from '../router.js'
|
||||
import store from './main.js'
|
||||
|
||||
const audio = new Audio()
|
||||
audio.ondurationchange = () => store.commit('player/duration', audio.duration)
|
||||
audio.onended = () => store.dispatch('player/stop')
|
||||
audio.onloadeddata = () => store.commit('player/loaded', true)
|
||||
audio.onplay = () => store.commit('player/paused', false)
|
||||
audio.onpause = () => store.commit('player/paused', true)
|
||||
audio.onseeked = () => store.commit('player/currentTime', audio.currentTime)
|
||||
audio.ontimeupdate = () => store.commit('player/currentTime', audio.currentTime)
|
||||
audio.onvolumechange = () => store.commit('player/volume', audio.volume)
|
||||
|
||||
export const player = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
currentTime: null,
|
||||
duration: null,
|
||||
episode: null,
|
||||
loaded: false,
|
||||
paused: null,
|
||||
podcastUrl: null,
|
||||
volume: null,
|
||||
},
|
||||
mutations: {
|
||||
currentTime: (state, currentTime) => {
|
||||
state.currentTime = currentTime
|
||||
},
|
||||
duration: (state, duration) => {
|
||||
state.duration = duration
|
||||
},
|
||||
load: (state, episode) => {
|
||||
state.episode = episode
|
||||
|
||||
if (episode) {
|
||||
state.podcastUrl = router.currentRoute.params.url
|
||||
audio.src = episode.episodeUrl
|
||||
audio.load()
|
||||
audio.play()
|
||||
} else {
|
||||
state.loaded = false
|
||||
state.podcastUrl = null
|
||||
audio.src = ''
|
||||
}
|
||||
},
|
||||
loaded: (state, loaded) => {
|
||||
state.loaded = loaded
|
||||
},
|
||||
paused: (state, paused) => {
|
||||
state.paused = paused
|
||||
},
|
||||
volume: (state, volume) => {
|
||||
state.volume = volume
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
pause: (context) => {
|
||||
audio.pause()
|
||||
axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [{
|
||||
podcast: context.state.podcastUrl,
|
||||
episode: context.state.episode.episodeUrl,
|
||||
guid: context.state.episode.episodeGuid,
|
||||
action: 'play',
|
||||
timestamp: formatEpisodeTimestamp(new Date()),
|
||||
started: Math.round(context.state.episode.episodeAction ? context.state.episode.episodeAction.started : 0),
|
||||
position: Math.round(audio.currentTime),
|
||||
total: Math.round(audio.duration),
|
||||
}])
|
||||
},
|
||||
play: () => audio.play(),
|
||||
seek: (context, currentTime) => {
|
||||
audio.currentTime = currentTime
|
||||
},
|
||||
stop: (context) => {
|
||||
context.dispatch('pause')
|
||||
context.commit('load', null)
|
||||
},
|
||||
volume: (context, volume) => {
|
||||
audio.volume = volume
|
||||
},
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue
Block a user