Rewrite player
All checks were successful
repod / nextcloud (push) Successful in 59s
repod / nodejs (push) Successful in 1m29s

This commit is contained in:
Michel Roux 2023-08-29 00:47:22 +02:00
parent 32cf9642c6
commit 16687ee669
15 changed files with 151 additions and 172 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<ul :style="{marginBottom: currentEpisode ? '6rem' : 'auto'}"> <ul :style="{marginBottom: episode ? '6rem' : 'auto'}">
<NcListItem v-for="feed in feeds" <NcListItem v-for="feed in feeds"
:key="feed.link" :key="feed.link"
:details="formatTimeAgo(new Date(feed.fetchedAtUnix*1000))" :details="formatTimeAgo(new Date(feed.fetchedAtUnix*1000))"
@ -43,7 +43,7 @@ export default {
} }
}, },
computed: { computed: {
currentEpisode() { episode() {
return this.$store.state.player.episode return this.$store.state.player.episode
}, },
}, },

View File

@ -17,7 +17,7 @@
{{ formatTimer(new Date(episode.episodeDuration*1000)) }} {{ formatTimer(new Date(episode.episodeDuration*1000)) }}
</template> </template>
<template #actions> <template #actions>
<NcActionButton @click="isCurrentEpisode(episode) ? play(null) : play(episode)"> <NcActionButton @click="isCurrentEpisode(episode) ? load(null) : load(episode)">
<template #icon> <template #icon>
<PlayButton v-if="!isCurrentEpisode(episode)" :size="20" /> <PlayButton v-if="!isCurrentEpisode(episode)" :size="20" />
<StopButton v-if="isCurrentEpisode(episode)" :size="20" /> <StopButton v-if="isCurrentEpisode(episode)" :size="20" />
@ -81,8 +81,8 @@ export default {
isCurrentEpisode(episode) { isCurrentEpisode(episode) {
return this.currentEpisode && this.currentEpisode.episodeUrl === episode.episodeUrl return this.currentEpisode && this.currentEpisode.episodeUrl === episode.episodeUrl
}, },
play(episode) { load(episode) {
this.$store.commit('player/play', episode) this.$store.commit('player/load', episode)
}, },
}, },
} }

View File

@ -1,29 +1,13 @@
<template> <template>
<div v-if="episode" class="footer"> <div v-if="player.episode" class="footer">
<audio id="audio-player" <NcLoadingIcon v-if="!player.loaded" />
autoplay <ProgressBar v-if="player.loaded" />
preload <div v-if="player.loaded" class="player">
:src="episode.episodeUrl" <img :src="player.episode.episodeImage">
@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">
<Infos /> <Infos />
<Controls :paused="paused" @stop="stop" /> <Controls />
<Timer class="timer" <Timer class="timer" />
:current-time="currentTime" <Volume class="volume" />
:duration="duration" />
<Volume class="volume" :volume="volume" />
</div> </div>
</div> </div>
</template> </template>
@ -35,10 +19,6 @@ import { NcLoadingIcon } from '@nextcloud/vue'
import ProgressBar from './ProgressBar.vue' import ProgressBar from './ProgressBar.vue'
import Timer from './Timer.vue' import Timer from './Timer.vue'
import Volume from './Volume.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 { export default {
name: 'Bar', name: 'Bar',
@ -50,56 +30,9 @@ export default {
Timer, Timer,
Volume, Volume,
}, },
data() {
return {
currentTime: 0,
duration: 0,
loading: true,
paused: false,
url: atob(this.$route.params.url),
volume: 1,
}
},
computed: { computed: {
audio() { player() {
return document.getElementById('audio-player') return this.$store.state.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'))
}
}, },
}, },
} }

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="controls"> <div class="controls">
<PauseButton v-if="!paused" <PauseButton v-if="!player.paused"
class="pointer" class="pointer"
:size="50" :size="50"
@click="() => audio.pause()" /> @click="$store.dispatch('player/pause')" />
<PlayButton v-if="paused" <PlayButton v-if="player.paused"
class="pointer" class="pointer"
:size="50" :size="50"
@click="() => audio.play()" /> @click="$store.dispatch('player/play')" />
<StopButton class="pointer" <StopButton class="pointer"
:size="30" :size="30"
@click="$emit('stop')" /> @click="$store.dispatch('player/stop')" />
</div> </div>
</template> </template>
@ -26,15 +26,9 @@ export default {
PlayButton, PlayButton,
StopButton, StopButton,
}, },
props: {
paused: {
type: Boolean,
required: true,
},
},
computed: { computed: {
audio() { player() {
return document.getElementById('audio-player') return this.$store.state.player
}, },
}, },
} }

View File

@ -1,10 +1,10 @@
<template> <template>
<div> <div>
<a :href="episode.episodeLink" target="_blank"> <a :href="player.episode.episodeLink" target="_blank">
<strong>{{ episode.episodeName }}</strong> <strong>{{ player.episode.episodeName }}</strong>
</a> </a>
<router-link :to="podcastUrl"> <router-link :to="player.podcastUrl">
<i>{{ episode.podcastName }}</i> <i>{{ player.episode.podcastName }}</i>
</router-link> </router-link>
</div> </div>
</template> </template>
@ -12,14 +12,9 @@
<script> <script>
export default { export default {
name: 'Infos', name: 'Infos',
data() {
return {
podcastUrl: atob(this.$route.params.url),
}
},
computed: { computed: {
episode() { player() {
return this.$store.state.player.episode return this.$store.state.player
}, },
}, },
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div @click="(event) => audio.currentTime = event.x * duration / event.target.offsetWidth"> <div @click="(event) => $store.dispatch('player/seek', event.x * player.duration / event.target.offsetWidth)">
<NcProgressBar size="medium" :value="currentTime * 100 / duration" /> <NcProgressBar size="medium" :value="player.currentTime * 100 / player.duration" />
</div> </div>
</template> </template>
@ -12,19 +12,9 @@ export default {
components: { components: {
NcProgressBar, NcProgressBar,
}, },
props: {
currentTime: {
type: Number,
required: true,
},
duration: {
type: Number,
required: true,
},
},
computed: { computed: {
audio() { player() {
return document.getElementById('audio-player') return this.$store.state.player
}, },
}, },
} }

View File

@ -1,8 +1,8 @@
<template> <template>
<div> <div>
<span>{{ formatTimer(new Date(currentTime*1000)) }}</span> <span>{{ formatTimer(new Date(player.currentTime*1000)) }}</span>
<span>/</span> <span>/</span>
<span>{{ formatTimer(new Date(duration*1000)) }}</span> <span>{{ formatTimer(new Date(player.duration*1000)) }}</span>
</div> </div>
</template> </template>
@ -11,14 +11,9 @@ import { formatTimer } from '../../utils/time.js'
export default { export default {
name: 'Timer', name: 'Timer',
props: { computed: {
currentTime: { player() {
type: Number, return this.$store.state.player
required: true,
},
duration: {
type: Number,
required: true,
}, },
}, },
methods: { methods: {

View File

@ -1,18 +1,18 @@
<template> <template>
<div> <div>
<VolumeHigh v-if="volume > 0.7" <VolumeHigh v-if="player.volume > 0.7"
class="pointer" class="pointer"
:size="30" :size="30"
@click="mute" /> @click="mute" />
<VolumeLow v-if="volume > 0 && volume <= 0.3" <VolumeLow v-if="player.volume > 0 && player.volume <= 0.3"
class="pointer" class="pointer"
:size="30" :size="30"
@click="mute" /> @click="mute" />
<VolumeMedium v-if="volume > 0.3 && volume <= 0.7" <VolumeMedium v-if="player.volume > 0.3 && player.volume <= 0.7"
class="pointer" class="pointer"
:size="30" :size="30"
@click="mute" /> @click="mute" />
<VolumeMute v-if="volume == 0" <VolumeMute v-if="player.volume == 0"
class="pointer" class="pointer"
:size="30" :size="30"
@click="unmute" /> @click="unmute" />
@ -20,8 +20,8 @@
min="0" min="0"
step="0.1" step="0.1"
type="range" type="range"
:value="volume" :value="player.volume"
@change="(event) => audio.volume = event.target.value"> @change="(event) => $store.dispatch('player/volume', event.target.value)">
</div> </div>
</template> </template>
@ -39,29 +39,23 @@ export default {
VolumeMedium, VolumeMedium,
VolumeMute, VolumeMute,
}, },
props: {
volume: {
type: Number,
required: true,
},
},
data() { data() {
return { return {
volumeMuted: 0, volumeMuted: 0,
} }
}, },
computed: { computed: {
audio() { player() {
return document.getElementById('audio-player') return this.$store.state.player
}, },
}, },
methods: { methods: {
mute() { mute() {
this.volumeMuted = this.audio.volume this.volumeMuted = this.player.volume
this.audio.volume = 0 this.$store.dispatch('player/volume', 0)
}, },
unmute() { unmute() {
this.audio.volume = this.volumeMuted this.$store.dispatch('player/volume', this.volumeMuted)
}, },
}, },
} }

View File

@ -9,7 +9,7 @@
</NcAppNavigationNew> </NcAppNavigationNew>
</router-link> </router-link>
<NcLoadingIcon v-if="loading" /> <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" <Item v-for="subscriptionUrl of subscriptions"
:key="subscriptionUrl" :key="subscriptionUrl"
:url="subscriptionUrl" /> :url="subscriptionUrl" />
@ -45,7 +45,7 @@ export default {
} }
}, },
computed: { computed: {
currentEpisode() { episode() {
return this.$store.state.player.episode return this.$store.state.player.episode
}, },
subscriptions() { subscriptions() {

View File

@ -1,10 +1,9 @@
import Vuex, { Store } from 'vuex'
import { translate, translatePlural } from '@nextcloud/l10n' import { translate, translatePlural } from '@nextcloud/l10n'
import App from './App.vue' import App from './App.vue'
import Vue from 'vue' import Vue from 'vue'
import { generateFilePath } from '@nextcloud/router' import { generateFilePath } from '@nextcloud/router'
import modules from './modules/index.js'
import router from './router.js' import router from './router.js'
import store from './store/main.js'
// eslint-disable-next-line // eslint-disable-next-line
__webpack_public_path__ = generateFilePath(appName, '', 'js/') __webpack_public_path__ = generateFilePath(appName, '', 'js/')
@ -13,12 +12,9 @@ const t = (...args) => translate('repod', ...args)
const n = (...args) => translatePlural('repod', ...args) const n = (...args) => translatePlural('repod', ...args)
Vue.mixin({ methods: { t, n } }) Vue.mixin({ methods: { t, n } })
Vue.use(Vuex)
Vue.prototype.AppConfig = window.oc_appconfig Vue.prototype.AppConfig = window.oc_appconfig
const store = new Store({ modules })
export default new Vue({ export default new Vue({
el: '#content', el: '#content',
router, router,

View File

@ -1,7 +0,0 @@
import { player } from './player.js'
import { subscriptions } from './subscriptions.js'
export default {
player,
subscriptions,
}

View File

@ -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
View 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
View 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
},
},
}