diff --git a/appinfo/routes.php b/appinfo/routes.php index 630fb091414e72bbb24168d8741e4a27cb8e8c38..63e76b9f904144e949cb68c1a37522bf8ebb3c71 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -23,7 +23,8 @@ return [ 'resources' => [ - 'show' => ['url' => '/api/shows'], + 'show' => ['url' => '/api/shows'], + 'episode' => ['url' => '/api/episodes'], ], 'routes' => [ diff --git a/lib/Controller/EpisodeController.php b/lib/Controller/EpisodeController.php new file mode 100644 index 0000000000000000000000000000000000000000..64181bfb6ac1167e05fc8e27236790815a9ac9e0 --- /dev/null +++ b/lib/Controller/EpisodeController.php @@ -0,0 +1,97 @@ +<?php + +/** + * Podcast App + * + * @author Jonas Heinrich + * @copyright 2021 Jonas Heinrich <onny@project-insanity.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Podcast\Controller; + +use OCA\Podcast\AppInfo\Application; +use OCA\Podcast\Service\EpisodeService; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\DataResponse; +use OCP\IRequest; + +class EpisodeController extends Controller { + /** @var EpisodesService */ + private $service; + + /** @var string */ + private $userId; + + use Errors; + + public function __construct(IRequest $request, + EpisodeService $service, + $userId) { + parent::__construct(Application::APP_ID, $request); + $this->service = $service; + $this->userId = $userId; + } + + /** + * @NoAdminRequired + */ + public function index(): DataResponse { + return new DataResponse($this->service->findAll($this->userId)); + } + + /** + * @NoAdminRequired + */ + public function show(int $id): DataResponse { + return $this->handleNotFound(function () use ($id) { + return $this->service->find($id, $this->userId); + }); + } + + /** + * @NoAdminRequired + */ + public function create(int $id, string $imgurl, string $author, + string $title, string $pubdate, string $duration, string $playtime, + string $lastplayed, string $enclosure): DataResponse { + return new DataResponse($this->service->create($id, $imgurl, $author, + $title, $pubdate, $duration, $playtime, $lastplayed, $enclosure, + $this->userId)); + } + + /** + * @NoAdminRequired + */ + public function update(int $id, string $imgurl, string $author, + string $title, string $pubdate, string $duration, string $playtime, + string $lastplayed, string $enclosure): DataResponse { + return $this->handleNotFound(function () use ($id, $imgurl, $author, + $title, $pubdate, $duration, $playtime, $lastplayed, $enclosure) { + return $this->service->update($id, $imgurl, $author, $title, $pubdate, + $duration, $playtime, $lastplayed, $enclosure, $this->userId); + }); + } + + /** + * @NoAdminRequired + */ + public function destroy(int $id): DataResponse { + return $this->handleNotFound(function () use ($id) { + return $this->service->delete($id, $this->userId); + }); + } +} diff --git a/lib/Controller/ShowController.php b/lib/Controller/ShowController.php index 086acb889811e574283e8e032d95ac058f9fadac..6e62bb1fb233e428e0144d5c216ab44f476684ed 100644 --- a/lib/Controller/ShowController.php +++ b/lib/Controller/ShowController.php @@ -74,9 +74,12 @@ class ShowController extends Controller { /** * @NoAdminRequired */ - public function update(int $id, string $title, string $htmlURL, string $smallImageURL, string $categories, string $lastpub, string $description, string $author): DataResponse { - return $this->handleNotFound(function () use ($id, $title, $htmlURL, $smallImageURL, $categories, $lastpub, $description, $author) { - return $this->service->update($id, $title, $htmlURL, $smallImageURL, $categories, $lastpub, $description, $author, $this->userId); + public function update(int $id, string $imgurl, string $author, + string $title): DataResponse { + return $this->handleNotFound(function () use ($id, $imgurl, $author, + $title) { + return $this->service->update($id, $imgurl, $author, $title, + $this->userId); }); } diff --git a/lib/Db/Episode.php b/lib/Db/Episode.php new file mode 100644 index 0000000000000000000000000000000000000000..470478f7fb9743e3b94bbef5b49902b3b6acb176 --- /dev/null +++ b/lib/Db/Episode.php @@ -0,0 +1,54 @@ +<?php + +/** + * Podcast App + * + * @author Jonas Heinrich + * @copyright 2021 Jonas Heinrich <onny@project-insanity.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Podcast\Db; + +use JsonSerializable; + +use OCP\AppFramework\Db\Entity; + +class Episode extends Entity implements JsonSerializable { + protected $imgurl; + protected $author; + protected $title; + protected $pubdate; + protected $duration; + protected $playtime; + protected $lastplayed; + protected $enclosure; + protected $userId; + + public function jsonSerialize(): array { + return [ + 'id' => $this->id, + 'imgurl' => $this->imgurl, + 'author' => $this->author, + 'title' => $this->title, + 'pubdate' => $this->pubdate, + 'duration' => $this->duration, + 'playtime' => $this->playtime, + 'lastplayed' => $this->lastplayed, + 'enclosure' => $this->enclosure, + ]; + } +} diff --git a/lib/Db/EpisodeMapper.php b/lib/Db/EpisodeMapper.php new file mode 100644 index 0000000000000000000000000000000000000000..654242d59e57437ff7e33111c93afa58d8c40997 --- /dev/null +++ b/lib/Db/EpisodeMapper.php @@ -0,0 +1,66 @@ +<?php + +/** + * Podcast App + * + * @author Jonas Heinrich + * @copyright 2021 Jonas Heinrich <onny@project-insanity.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Podcast\Db; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; + +class EpisodeMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'episodes', Episode::class); + } + + /** + * @param int $id + * @param string $userId + * @return Entity|Episode + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException + * @throws DoesNotExistException + */ + public function find(int $id, string $userId): Episode { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('episodes') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))); + return $this->findEntity($qb); + } + + /** + * @param string $userId + * @return array + */ + public function findAll(string $userId): array { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('episodes') + ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))); + return $this->findEntities($qb); + } +} diff --git a/lib/Migration/Version000000Date20181013124731.php b/lib/Migration/Version000000Date20181013124731.php index a0b60ccee3599b6a605764788684fad760416684..234122265ac5b165bda118e3fcbff485d84ef02d 100644 --- a/lib/Migration/Version000000Date20181013124731.php +++ b/lib/Migration/Version000000Date20181013124731.php @@ -57,6 +57,26 @@ class Version000000Date20181013124731 extends SimpleMigrationStep { $table->addIndex(['user_id'], 'shows_user_id_index'); } + if (!$schema->hasTable('episodes')) { + $table = $schema->createTable('episodes'); + $table->addColumn('id', 'integer', [ + 'notnull' => true, + ]); + $table->addColumn('user_id', 'string', [ + 'notnull' => true, + ]); + $table->addColumn('imgurl', 'string'); + $table->addColumn('author', 'string'); + $table->addColumn('title', 'string'); + $table->addColumn('pubdate', 'string'); + $table->addColumn('duration', 'string'); + $table->addColumn('playtime', 'string'); + $table->addColumn('lastplayed', 'string'); + $table->addColumn('enclosure', 'string'); + $table->setPrimaryKey(['id']); + $table->addIndex(['user_id'], 'shows_user_id_index'); + } + return $schema; } } diff --git a/lib/Service/EpisodeService.php b/lib/Service/EpisodeService.php new file mode 100644 index 0000000000000000000000000000000000000000..f5bb1509000aff570b11d9f32dd245797e4a7133 --- /dev/null +++ b/lib/Service/EpisodeService.php @@ -0,0 +1,112 @@ +<?php + +/** + * Podcast App + * + * @author Jonas Heinrich + * @copyright 2021 Jonas Heinrich <onny@project-insanity.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Podcast\Service; + +use Exception; + +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\MultipleObjectsReturnedException; + +use OCA\Podcast\Db\Episode; +use OCA\Podcast\Db\EpisodeMapper; + +class EpisodeService { + + /** @var EpisodeMapper */ + private $mapper; + + public function __construct(EpisodeMapper $mapper) { + $this->mapper = $mapper; + } + + public function findAll(string $userId): array { + return $this->mapper->findAll($userId); + } + + private function handleException(Exception $e): void { + if ($e instanceof DoesNotExistException || + $e instanceof MultipleObjectsReturnedException) { + throw new EpisodeNotFound($e->getMessage()); + } else { + throw $e; + } + } + + public function find($id, $userId) { + try { + return $this->mapper->find($id, $userId); + + // in order to be able to plug in different storage backends like files + // for instance it is a good idea to turn storage related exceptions + // into service related exceptions so controllers and service users + // have to deal with only one type of exception + } catch (Exception $e) { + $this->handleException($e); + } + } + + public function create($id, $imgurl, $author, $title, $pubdate, $duration, + $playtime, $lastplayed, $enclosure, $userId) { + $episode = new Episode(); + $episode->setId($id); + $episode->setImgurl($imgurl); + $episode->setAuthor($author); + $episode->setTitle($title); + $episode->setPubdate($pubdate); + $episode->setDuration($duration); + $episode->setPlaytime($playtime); + $episode->setLastplayed($lastplayed); + $episode->setEnclosure($enclosure); + $episode->setUserId($userId); + return $this->mapper->insert($episode); + } + + public function update($id, $imgurl, $author, $title, $pubdate, $duration, + $playtime, $lastplayed, $enclosure, $userId) { + try { + $episode = $this->mapper->find($id, $userId); + $episode->setImgurl($imgurl); + $episode->setAuthor($author); + $episode->setTitle($title); + $episode->setPubdate($pubdate); + $episode->setDuration($duration); + $episode->setPlaytime($playtime); + $episode->setLastplayed($lastplayed); + $episode->setEnclosure($enclosure); + return $this->mapper->update($episode); + } catch (Exception $e) { + $this->handleException($e); + } + } + + public function delete($id, $userId) { + try { + $episode = $this->mapper->find($id, $userId); + $this->mapper->delete($episode); + return $episode; + } catch (Exception $e) { + $this->handleException($e); + } + } +} diff --git a/lib/Service/ShowService.php b/lib/Service/ShowService.php index 839a106ac385f91b34ca7f428750c779a6a5b0ce..d714c8889f3d44cd6b4d440232bdeefaf3e98b50 100644 --- a/lib/Service/ShowService.php +++ b/lib/Service/ShowService.php @@ -76,16 +76,12 @@ class ShowService { return $this->mapper->insert($show); } - public function update($id, $title, $htmlURL, $smallImageURL, $categories, $lastpub, $description, $author, $userId) { + public function update($id, $imgurl, $author, $title, $userId) { try { $show = $this->mapper->find($id, $userId); - $show->setTitle($title); - $show->setHtmlURL($htmlURL); - $show->setSmallImageURL($smallImageURL); - $show->setCategories($categories); - $show->setLastpub($lastpub); - $show->setDescription($description); + $show->setImgurl($imgurl); $show->setAuthor($author); + $show->setTitle($title); return $this->mapper->update($show); } catch (Exception $e) { $this->handleException($e); diff --git a/src/App.vue b/src/App.vue index 2ab917cf37ab94f38fd065876f1a029a6c35c498..ccb1c4f4447847255d2e7a381ed25ab8732ea2c9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -43,6 +43,7 @@ export default { }, created() { this.$store.dispatch('loadShows') + this.$store.dispatch('loadEpisodes') this.$store.dispatch('getVolumeState') }, } diff --git a/src/components/MediaHeader.vue b/src/components/MediaHeader.vue index 9047894c2aaf272c9b32523b6315c6c7f6ce9f04..db48e5c454cdf65297f0551355ffd16db191c506 100644 --- a/src/components/MediaHeader.vue +++ b/src/components/MediaHeader.vue @@ -59,7 +59,9 @@ <script> import vueShowMoreText from 'vue-show-more-text' -import { getCategoryName } from '../services/podcastApi' +import { ShowApi } from './../services/ShowApi' + +const apiClient = new ShowApi() export default { name: 'MediaHeader', @@ -119,7 +121,7 @@ export default { this.$emit('doSubscribe') }, getCategoryName(categoryid) { - return getCategoryName(categoryid) + return apiClient.getCategoryName(categoryid) }, }, } diff --git a/src/services/EpisodeApi.js b/src/services/EpisodeApi.js new file mode 100644 index 0000000000000000000000000000000000000000..6303885e6e8d3bd7e50496a40c6f09d2718f9da3 --- /dev/null +++ b/src/services/EpisodeApi.js @@ -0,0 +1,89 @@ +/* + * @copyright Copyright (c) 2021 Jonas Heinrich <onny@project-insanity.org> + * + * @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/>. + * + */ + +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' + +const requesttoken = axios.defaults.headers.requesttoken + +export class EpisodeApi { + + url(url) { + url = `/apps/podcast/api${url}` + return generateUrl(url) + } + + addEpisode(episode) { + episode = { + id: episode.id, + imgurl: episode.smallImageURL, + title: episode.title, + author: episode.author, + } + axios.defaults.headers.requesttoken = requesttoken + return axios.post(this.url('/episodes'), episode) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + + removeEpisode(episode) { + axios.defaults.headers.requesttoken = requesttoken + return axios.delete(this.url(`/episodes/${episode.id}`)) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + + loadEpisodes(episode) { + axios.defaults.headers.requesttoken = requesttoken + return axios.get(this.url('/episodes')) + .then( + (response) => { + return Promise.resolve(response.data) + }, + (err) => { + return Promise.reject(err) + } + ) + .catch((err) => { + return Promise.reject(err) + }) + } + +} diff --git a/src/services/player.js b/src/services/Player.js similarity index 53% rename from src/services/player.js rename to src/services/Player.js index 55dce688f88d04353f332f21aa7009f7ba1830da..93ea8a254158890658db7aa56b7111f22219702e 100644 --- a/src/services/player.js +++ b/src/services/Player.js @@ -23,39 +23,38 @@ import { Howl, Howler } from 'howler' import { showError } from '@nextcloud/dialogs' -export let audioPlayer -audioPlayer = null +let audioPlayer = null -export function doPlay(episode) { - const vm = this +export class Player { - vm.$store.dispatch('isBuffering', true) - - if (audioPlayer !== null) { - audioPlayer.fade(vm.player.volume, 0, 500) + play(src) { + this.$store.dispatch('isBuffering', true) + if (audioPlayer !== null) { + audioPlayer.fade(this.player.volume, 0, 500) + } + this.$store.dispatch('setTitle', 'test') + Howler.unload() + audioPlayer = new Howl({ + src, + html5: true, + volume: this.player.volume, + onplay() { + this.$store.dispatch('isPlaying', true) + this.$store.dispatch('isBuffering', false) + }, + onpause() { + this.$store.dispatch('isPlaying', false) + this.$store.dispatch('isBuffering', false) + }, + onend() { + showError(t('podcast', 'Lost connection to podcast station, retrying ...')) + this.$store.dispatch('isPlaying', false) + this.$store.dispatch('isBuffering', true) + }, + }) + audioPlayer.unload() + audioPlayer.play() + audioPlayer.fade(0, this.player.volume, 500) } - vm.$store.dispatch('setTitle', episode.title) - Howler.unload() - audioPlayer = new Howl({ - src: episode.enclosure, - 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) } diff --git a/src/services/ShowApi.js b/src/services/ShowApi.js index 070508c4718b94cfadaf77f3de249328e338bd43..f479815a69bd795ffe43f17596fa593ea45e1b3f 100644 --- a/src/services/ShowApi.js +++ b/src/services/ShowApi.js @@ -24,6 +24,7 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' const requesttoken = axios.defaults.headers.requesttoken +const categories = require('../assets/categories.json') export class ShowApi { @@ -86,4 +87,23 @@ export class ShowApi { }) } + getCategoryName(categoryid) { + for (let i = 0; i < categories.data.length; i++) { + const obj = categories.data[i] + if (obj.id === categoryid) { + return obj.name + } + if ('subcategories' in categories.data[i]) { + const subcategories = categories.data[i].subcategories + for (let i = 0; i < subcategories.length; i++) { + const obj = subcategories[i] + if (obj.id === categoryid) { + return obj.name + } + } + } + } + return categoryid + } + } diff --git a/src/services/podcastApi.js b/src/services/podcastApi.js deleted file mode 100644 index b7748f81cfe8319929cf69df9c60a40612388523..0000000000000000000000000000000000000000 --- a/src/services/podcastApi.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * @copyright Copyright (c) 2021 Jonas Heinrich <onny@project-insanity.org> - * - * @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/>. - * - */ - -const categories = require('../assets/categories.json') - -export function getCategoryName(categoryid) { - for (let i = 0; i < categories.data.length; i++) { - const obj = categories.data[i] - if (obj.id === categoryid) { - return obj.name - } - if ('subcategories' in categories.data[i]) { - const subcategories = categories.data[i].subcategories - for (let i = 0; i < subcategories.length; i++) { - const obj = subcategories[i] - if (obj.id === categoryid) { - return obj.name - } - } - } - } - return categoryid -} diff --git a/src/store/episode.js b/src/store/episode.js new file mode 100644 index 0000000000000000000000000000000000000000..2b0899c097f8c2412f66bc2f659f099e9096733d --- /dev/null +++ b/src/store/episode.js @@ -0,0 +1,75 @@ +/* + * @copyright Copyright (c) 2021 Jonas Heinrich <onny@project-insanity.org> + * + * @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/>. + * + */ + +import { EpisodeApi } from './../services/EpisodeApi' + +const apiClient = new EpisodeApi() + +export default { + state: { + episodes: [], + }, + getters: { + episodesQueue: state => { + return state.episodes + }, + episodeById: state => (id) => { + return state.episodes.find((episode) => episode.id === id) + }, + episodeExists: state => (id) => { + return state.episodes.some((episode) => episode.id === id) + }, + + }, + mutations: { + addEpisode(state, episode) { + state.episodes.push(episode) + }, + removeEpisode(state, episode) { + const existingIndex = state.episodes.findIndex(_episode => _episode.id === episode.id) + if (existingIndex !== -1) { + state.episodes.splice(existingIndex, 1) + } + }, + setEpisodes(state, episodes) { + state.episodes = episodes + }, + }, + actions: { + async loadEpisodes({ commit }) { + const episodes = await apiClient.loadEpisodes() + commit('setEpisodes', episodes) + }, + addEpisode({ commit }, episode) { + apiClient.addEpisode(episode) + .then((episode) => { + commit('addEpisode', episode) + }) + }, + removeEpisode({ commit }, episode) { + apiClient.removeEpisode(episode) + .then((episode) => { + commit('removeEpisode', episode) + }) + }, + }, +} diff --git a/src/store/main.js b/src/store/main.js index 8aa08e653cbdb72bb54a8601794aab23cd2f50b3..d3260ea7731bff1064096e9363492ed0504e4e9d 100644 --- a/src/store/main.js +++ b/src/store/main.js @@ -23,106 +23,16 @@ import Vue from 'vue' import Vuex from 'vuex' -import axios from '@nextcloud/axios' -import { generateUrl } from '@nextcloud/router' - import show from './show' import player from './player' +import episode from './episode' Vue.use(Vuex) -const requesttoken = axios.defaults.headers.requesttoken export default new Vuex.Store({ modules: { show, player, - }, - state: { - player: { - isPlaying: false, - isBuffering: false, - isMute: false, - isPaused: false, - volume: 0.5, - oldVolume: 0, - title: '', - }, - menu: 'top', - }, - mutations: { - isPlaying(state, playerState) { - state.player.isPlaying = playerState - }, - isBuffering(state, bufferingState) { - state.player.isBuffering = bufferingState - }, - changeVolume(state, volume) { - state.player.volume = volume - }, - toggleMute(state) { - if (state.player.isMute) { - state.player.volume = state.player.oldVolume - state.player.isMute = false - } else { - state.player.oldVolume = state.player.volume - state.player.volume = 0 - state.player.isMute = true - } - }, - togglePlay(state) { - if (state.player.isPlaying) { - state.player.isPlaying = false - state.player.isPaused = true - } else { - state.player.isPlaying = true - state.player.isPaused = false - } - }, - setTitle(state, title) { - state.player.title = title - }, - setVolumeState(state, volumeState) { - axios.defaults.headers.requesttoken = requesttoken - axios.post(generateUrl('/apps/podcast/settings/volumeState'), { - volumeState, - }) - }, - getVolumeState(state) { - axios.defaults.headers.requesttoken = requesttoken - axios - .get(generateUrl('/apps/podcast/settings/volumeState')) - .then(async response => { - const { - data: { volumeState: value }, - } = response - state.player.volume = value - }) - }, - }, - actions: { - isPlaying(context, playerState) { - context.commit('isPlaying', playerState) - }, - isBuffering(context, bufferingState) { - context.commit('isBuffering', bufferingState) - }, - changeVolume(context, volume) { - context.commit('changeVolume', volume) - }, - toggleMute(context) { - context.commit('toggleMute') - }, - togglePlay(context) { - context.commit('togglePlay') - }, - setTitle(context, title) { - context.commit('setTitle', title) - }, - setVolumeState(context, volumeState) { - context.commit('setVolumeState', volumeState) - }, - getVolumeState(context) { - context.commit('getVolumeState') - }, + episode, }, }) diff --git a/src/store/player.js b/src/store/player.js new file mode 100644 index 0000000000000000000000000000000000000000..45944b0b0df1fa87bdc69ba9cc164bdbc6dc57a8 --- /dev/null +++ b/src/store/player.js @@ -0,0 +1,114 @@ +/* + * @copyright Copyright (c) 2021 Jonas Heinrich <onny@project-insanity.org> + * + * @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/>. + * + */ + +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' +const requesttoken = axios.defaults.headers.requesttoken + +export default { + state: { + isPlaying: false, + isBuffering: false, + isMute: false, + isPaused: false, + volume: 0.5, + oldVolume: 0, + title: '', + episodePlaying: null, + }, + mutations: { + isPlaying(state, playerState) { + state.player.isPlaying = playerState + }, + isBuffering(state, bufferingState) { + state.player.isBuffering = bufferingState + }, + changeVolume(state, volume) { + state.player.volume = volume + }, + toggleMute(state) { + if (state.player.isMute) { + state.player.volume = state.player.oldVolume + state.player.isMute = false + } else { + state.player.oldVolume = state.player.volume + state.player.volume = 0 + state.player.isMute = true + } + }, + togglePlay(state) { + if (state.player.isPlaying) { + state.player.isPlaying = false + state.player.isPaused = true + } else { + state.player.isPlaying = true + state.player.isPaused = false + } + }, + setTitle(state, title) { + state.player.title = title + }, + setVolumeState(state, volumeState) { + axios.defaults.headers.requesttoken = requesttoken + axios.post(generateUrl('/apps/podcast/settings/volumeState'), { + volumeState, + }) + }, + getVolumeState(state) { + axios.defaults.headers.requesttoken = requesttoken + axios + .get(generateUrl('/apps/podcast/settings/volumeState')) + .then(async response => { + const { + data: { volumeState: value }, + } = response + state.player.volume = value + }) + }, + }, + actions: { + isPlaying(context, playerState) { + context.commit('isPlaying', playerState) + }, + isBuffering(context, bufferingState) { + context.commit('isBuffering', bufferingState) + }, + changeVolume(context, volume) { + context.commit('changeVolume', volume) + }, + toggleMute(context) { + context.commit('toggleMute') + }, + togglePlay(context) { + context.commit('togglePlay') + }, + setTitle(context, title) { + context.commit('setTitle', title) + }, + setVolumeState(context, volumeState) { + context.commit('setVolumeState', volumeState) + }, + getVolumeState(context) { + context.commit('getVolumeState') + }, + }, +} diff --git a/src/views/Episode.vue b/src/views/Episode.vue index 13e0a965cd9fedf9a6dc60f93587b37cdd2e171f..61f04677e3f54428784a441f7ccaa318f39bf69e 100644 --- a/src/views/Episode.vue +++ b/src/views/Episode.vue @@ -30,7 +30,9 @@ :author="podcastName" :htmlurl="`#/browse/show/${podcastId}`" :isshow="false"> - <button class="icon-play-white podcastButton button primary new-button"> + <button + class="icon-play-white podcastButton button primary new-button" + @click="doPlay"> {{ t('podcast', 'Play episode') }} </button> <div class="episodeDetails"> @@ -84,7 +86,10 @@ import MediaHeader from '../components/MediaHeader' import TimeAgo from 'javascript-time-ago' import { FyydApi } from './../services/FyydApi' +import { Player } from './../services/Player' + const fyydClient = new FyydApi() +const player = new Player() const timeAgo = new TimeAgo('en-US') @@ -110,6 +115,10 @@ export default { }, methods: { + doPlay() { + player.play(this.episode.enclosure) + }, + readableDuration(timestamp) { return timestamp.split('.')[0] }, diff --git a/src/views/Show.vue b/src/views/Show.vue index f0f0d6bb2466d0a02d367927f311415029433895..112e27e0839deb348ea18f53d66d4032ddfd45ab 100644 --- a/src/views/Show.vue +++ b/src/views/Show.vue @@ -52,9 +52,9 @@ import Table from '../components/Table' import MediaHeader from '../components/MediaHeader' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import ShowEmpty from './placeholder/Show' -import { audioPlayer, doPlay } from '../services/player' import { FyydApi } from './../services/FyydApi' + const fyydClient = new FyydApi() export default { @@ -85,18 +85,6 @@ export default { this.podcastId = to.params.id this.init() }, - '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() - } - }, }, mounted() { this.initPage() @@ -129,10 +117,6 @@ export default { } }, - doPlay(episode) { - doPlay(episode) - }, - doSubscribe() { if (this.isSubscribed) { this.$store.dispatch('removeShow', this.podcast)