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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: {

View File

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

View File

@ -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() {

View File

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

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