<!-- - @copyright Copyright (c) 2020 Jonas Heinrich - - @author Jonas Heinrich <onny@project-insanity.org> - - @license GNU AGPL version 3 or any later version - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - --> <template> <Content app-name="podcast"> <Navigation :station-data="tableData" /> <AppContent> <div class="podcastHeader"> <div class="podcastImage" :style="{ backgroundImage: `url(${podcast.imgURL})` }" /> <div class="podcastDescription"> <h1>{{ podcast.title }}</h1> <div class="podcastAuthor"> by <a href="#">{{ podcast.author }}</a> </div> <div>{{ podcast.description }}</div> <ul v-for="(category, idx) in podcast.categories" :key="idx" :class="podcastCategory"> <li>{{ podcast.categories[idx] }}</li> </ul> </div> </div> <Table v-show="!pageLoading && podcast.episodes.length > 0" v-resize="onResize" :station-data="podcast.episodes" :favorites="favorites" @doPlay="doPlay" @doFavor="doFavor" @toggleSidebar="toggleSidebar" /> <EmptyContent v-if="pageLoading" icon="icon-loading" /> <EmptyContent v-if="tableData.length === 0 && !pageLoading" :icon="emptyContentIcon"> {{ emptyContentMessage }} <template #desc> {{ emptyContentDesc }} </template> </EmptyContent> </AppContent> <Sidebar :show-sidebar="showSidebar" :sidebar-station="sidebarStation" @toggleSidebar="toggleSidebar" /> </Content> </template> <script> import Content from '@nextcloud/vue/dist/Components/Content' import AppContent from '@nextcloud/vue/dist/Components/AppContent' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import Navigation from '../components/Navigation' import Table from '../components/Table' import Sidebar from '../components/Sidebar' import { Howl, Howler } from 'howler' import { generateUrl } from '@nextcloud/router' import { showError } from '@nextcloud/dialogs' import axios from '@nextcloud/axios' let audioPlayer = null const requesttoken = axios.defaults.headers.requesttoken export default { name: 'Episode', components: { Navigation, Content, AppContent, Table, EmptyContent, Sidebar, }, data: () => ({ podcast: {}, tableData: [], pageLoading: false, favorites: [], showSidebar: false, sidebarStation: {}, queryParams: {}, }), computed: { player() { return this.$store.state.player }, emptyContentMessage() { if (this.$route.name === 'FAVORITES') { return t('podcast', 'No favorites yet') } else if (this.$route.name === 'RECENT') { return t('podcast', 'No recent stations yet') } else if (this.$route.name === 'SEARCH') { return t('podcast', 'No search results') } return 'No stations here' }, emptyContentIcon() { if (this.$route.name === 'FAVORITES') { return 'icon-star' } else if (this.$route.name === 'RECENT') { return 'icon-recent' } else if (this.$route.name === 'SEARCH') { return 'icon-search' } return 'icon-podcast' }, emptyContentDesc() { if (this.$route.name === 'FAVORITES') { return t('podcast', 'Stations you mark as favorite will show up here') } else if (this.$route.name === 'RECENT') { return t('podcast', 'Stations you recently played will show up here') } else if (this.$route.name === 'SEARCH') { return t('podcast', 'No stations were found matching your search term') } return t('podcast', 'No stations here') }, }, watch: { $route: 'onRoute', 'player.volume'(newVolume, oldVolume) { if (audioPlayer !== null) { audioPlayer.volume(newVolume) } }, 'player.isPaused'(newState, oldState) { if (newState === true && audioPlayer !== null) { audioPlayer.pause() } else if (newState === false && audioPlayer !== null) { audioPlayer.play() } }, }, created() { this.loadSettings() this.loadFavorites() }, mounted() { this.onRoute() this.scroll() }, methods: { onResize({ width, height }) { const contentHeight = document.getElementById('app-content-vue').scrollHeight const tableHeight = height if (tableHeight < contentHeight) { this.preFill() } }, preFill() { const route = this.$route console.log(route) // this.queryPodcast(route.name) }, async onRoute() { this.tableData = [] this.pageLoading = true const podcastId = this.$route.params.id this.queryPodcast(podcastId) }, /** * Favor a new station by sending the information to the server * @param {Object} station Station object */ async doFavor(station) { if (this.favorites.flat().includes(station.stationuuid)) { let stationid = null try { for (let i = 0, len = this.favorites.length; i < len; i++) { if (station.stationuuid === this.favorites[i][1]) { stationid = this.favorites[i][0] } } axios.defaults.headers.requesttoken = requesttoken await axios .delete(generateUrl(`/apps/podcast/api/favorites/${stationid}`)) .then(response => { this.favorites = this.favorites.filter(item => item[1] !== station.stationuuid) }) } catch (error) { showError(t('podcast', 'Could not remove station from favorites')) } } else { try { let stationSrc = '' if (!station.url_resolved) { stationSrc = station.urlresolved } else { stationSrc = station.url_resolved } const stationMap = { id: -1, name: station.name.toString(), urlresolved: stationSrc.toString(), favicon: station.favicon.toString(), stationuuid: station.stationuuid.toString(), bitrate: station.bitrate.toString(), country: station.country.toString(), language: station.language.toString(), homepage: station.homepage.toString(), codec: station.codec.toString(), tags: station.tags.toString(), } axios.defaults.headers.requesttoken = requesttoken await axios .post(generateUrl('/apps/podcast/api/favorites'), stationMap) .then(response => { this.favorites.push([response.data.id, station.stationuuid]) }) } catch (error) { showError(t('podcast', 'Could not favor station')) } } }, /** * Start playing a podcast station and counting the playback * @param {Object} station Station object */ async doPlay(station) { const vm = this vm.$store.dispatch('isBuffering', true) if (audioPlayer !== null) { audioPlayer.fade(vm.player.volume, 0, 500) } vm.$store.dispatch('setTitle', station.name) let stationSrc = '' if (!station.url_resolved) { stationSrc = station.urlresolved } else { stationSrc = station.url_resolved } Howler.unload() audioPlayer = new Howl({ src: stationSrc, html5: true, volume: vm.player.volume, onplay() { vm.$store.dispatch('isPlaying', true) vm.$store.dispatch('isBuffering', false) }, onpause() { vm.$store.dispatch('isPlaying', false) vm.$store.dispatch('isBuffering', false) }, onend() { showError(t('podcast', 'Lost connection to podcast station, retrying ...')) vm.$store.dispatch('isPlaying', false) vm.$store.dispatch('isBuffering', true) }, }) audioPlayer.unload() audioPlayer.play() audioPlayer.fade(0, vm.player.volume, 500) /* Count click */ try { delete axios.defaults.headers.requesttoken axios.get(this.$apiUrl + '/json/url/' + station.stationuuid) } catch (error) { showError(t('podcast', 'Unable to count play on remote API')) } /* Put into recent stations */ try { let stationSrc = '' if (!station.url_resolved) { stationSrc = station.urlresolved } else { stationSrc = station.url_resolved } const stationMap = { id: -1, name: station.name.toString(), urlresolved: stationSrc.toString(), favicon: station.favicon.toString(), stationuuid: station.stationuuid.toString(), bitrate: station.bitrate.toString(), country: station.country.toString(), language: station.language.toString(), homepage: station.homepage.toString(), codec: station.codec.toString(), tags: station.tags.toString(), } axios.defaults.headers.requesttoken = requesttoken await axios .post(generateUrl('/apps/podcast/api/recent'), stationMap) } catch (error) { showError(t('podcast', 'Could not add station to recent list')) } }, async queryPodcast(podcastId) { const vm = this podcastId = 1084 const queryURI = 'https://api.fyyd.de/0.2/podcast/episodes?podcast_id=' + podcastId try { delete axios.defaults.headers.requesttoken await axios.get(queryURI) .then(function(response) { vm.processPodcast(response.data) }) } catch (error) { showError(t('podcast', 'Could not fetch stations from remote API')) } }, processPodcast(podcast) { this.podcast = podcast.data this.pageLoading = false }, /** * On scroll event, load more stations if bottom reached */ scroll() { window.onscroll = () => { if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) { const route = this.$route console.log(route) // this.queryPodcast(route.name) } } }, loadSettings() { // axios.defaults.headers.common = { // 'User-Agent': 'Nextcloud Podcast App/' + this.$version, // } this.$store.dispatch('getVolumeState') }, async loadFavorites() { const vm = this try { axios.defaults.headers.requesttoken = requesttoken await axios.get(generateUrl('/apps/podcast/api/favorites')) .then(function(response) { const favorites = [] for (let i = 0, len = response.data.length; i < len; i++) { favorites.push([response.data[i].id, response.data[i].stationuuid]) } vm.favorites = favorites }) } catch (error) { showError(t('podcast', 'Unable to load favorites')) } }, toggleSidebar(station = null) { if (station) { this.showSidebar = true this.sidebarStation = station } else { this.showSidebar = false } }, }, } </script> <style lang="scss"> @media only screen and (min-width: 1024px) { .app-navigation-toggle { display: none; } } </style>