Rewrite player
This commit is contained in:
parent
32cf9642c6
commit
16687ee669
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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'))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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: {
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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,
|
||||||
|
@ -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