diff --git a/src/App.vue b/src/App.vue
index 8124308..5a88f6e 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -23,11 +23,16 @@
-
+
+
-
-
-
+
+
+
+
+
@@ -54,20 +62,21 @@
+ >
+ {{ currentTime ? currentTime : "00:00"}}/{{duration ? duration : "00:00"}}
+
- {{ authorName }}
- {{ bookTitle }} - {{ currentChapter }}
+ {{ $store.getters.book.author }}
+ {{ $store.getters.book.title }} - {{ $store.getters.chapters[currentChapter].title }}
@@ -110,7 +119,9 @@
:class="{ 'mr-3': mdAndUp, 'ml-3': mdAndDown }"
:min="0"
:max="10"
+ thumb-label
hide-details
+ color="blue"
v-model="volume"
prepend-icon="mdi-volume-low"
append-icon="mdi-volume-high"
@@ -138,6 +149,7 @@
-->
+
@@ -146,11 +158,13 @@
import { ref } from "vue";
import { useDisplay } from "vuetify";
import MessageBox from "./components/MessageBox";
+ import SettingsDialog from "./components/SettingsDialog";
export default {
name: "App",
components: {
MessageBox,
+ SettingsDialog,
},
setup () {
@@ -180,15 +194,19 @@
appData: { name: "", net: "" },
backgroundImage: this.defaultBackground,
- authors: [],
+ authors: [],
+ autoBookmark: true,
- audioElement: null,
- status: 0,
- currentChapter: null,
- volume: 5,
+ audioElement: null,
+ currentChapter: null,
+ currentProgress: 0,
+ currentTime: "00:00",
+ playbackRate: 1,
+ status: 0,
+ volume: 5,
- scrolled: false,
- useWikipedia: true,
+ scrolled: false,
+ useWikipedia: true,
}
},
@@ -203,7 +221,26 @@
}
},
- volume () { this.updateVolume(); }
+ "$store.getters.darkTheme" (val) {
+ this.setTheme(val == true ? "dark" : "light");
+ localStorage.darkTheme = val;
+ },
+
+ "$store.getters.playbackRate" (val) {
+ this.playbackRate = val;
+ localStorage.playbackRate = val;
+ },
+
+ playbackRate (val) {
+ if (this.audioElement !== null) {
+ this.audioElement.playbackRate = val;
+ }
+ },
+
+ volume (val) {
+ this.updateVolume();
+ localStorage.volume = val;
+ }
},
computed: {
@@ -223,12 +260,35 @@
return (this.currentChapter !== null) && this.audioElement;
},
+ progress: {
+ get () {
+ return this.currentProgress;
+ },
+
+ set (val) {
+ if (this.audioElement !== null && val !== Math.round(this.currentProgress)) {
+ //console.log(this.audioElement);
+ //console.log(this.audioElement.duration);
+ this.currentProgress = val;
+ this.audioElement.currentTime = this.audioElement.duration * val / 100;
+ }
+ }
+ },
+
showProgress () {
return false;
}
},
mounted () {
+ let darkTheme = localStorage.darkTheme ? JSON.parse(localStorage.darkTheme) : false;
+ this.$store.commit("setDarkTheme", darkTheme);
+
+ let playbackRate = localStorage.playbackRate ? JSON.parse(localStorage.playbackRate) : 1;
+ this.$store.commit("setPlaybackRate", playbackRate);
+
+ this.volume = localStorage.volume ? JSON.parse(localStorage.volume) : 5;
+
this.appData.name = this.$t("appName");
this.appData.net = this.appConfig.appNet;
@@ -352,6 +412,12 @@
console.log(bmKey, bookmark);
+ if (bookmark !== null) {
+ this.loadChapter(bookmark.chapter, bookmark.progress);
+ } else {
+ this.loadChapter();
+ }
+
if (this.useWikipedia) {
this.fillWikiInfo(bookData.title, false);
}
@@ -412,16 +478,135 @@
})
},
+ loadChapter (index = 0, progress = 0, autoplay = false) {
+ if (this.audioElement) { this.audioElement.pause(); }
+ if (index >= this.$store.getters.chapters.length) { return false; } // show message?
+
+ this.currentChapter = index;
+ this.audioElement = new Audio(encodeURI(this.$store.getters.chapters[index].url));
+ this.audioElement.playbackRate = this.playbackRate;
+
+ this.$store.commit("setCurChapter", index);
+
+ this.updateVolume();
+
+ this.status = this.statuses.stopped;
+
+ this.audioElement.addEventListener("ended", this.loadNextChapter);
+ this.audioElement.ontimeupdate = this.updateProgress;
+
+ let component = this;
+
+ this.audioElement.addEventListener("durationchange", function () {
+ let min = parseInt(component.audioElement.duration / 60) < 10
+ ? "0" + parseInt(component.audioElement.duration / 60)
+ : parseInt(component.audioElement.duration / 60);
+ let sec = parseInt(component.audioElement.duration % 60) < 10
+ ? "0" + parseInt(component.audioElement.duration % 60)
+ : parseInt(component.audioElement.duration % 60);
+ component.duration = min + ":" + sec;
+ });
+
+ this.audioElement.addEventListener("canplay", function _listener () {
+ component.audioElement.currentTime = component.audioElement.duration * progress / 100;
+ component.audioElement.removeEventListener("canplay", _listener, true);
+ }, true);
+
+ if (autoplay) this.play();
+ },
+
+ loadNextChapter (autoplay = true) {
+ this.currentChapter++;
+ if (this.currentChapter >= this.$store.getters.chapters.length) {
+ this.currentChapter--;
+ return false;
+ }
+ this.loadChapter(this.currentChapter, 0, autoplay);
+ },
+
+ nextChapter () {
+ if (this.currentChapter < this.$store.getters.chapters.length - 1) {
+ this.currentChapter++;
+ this.loadChapter(this.currentChapter, 0, true);
+ }
+ },
+
onScroll () {
this.scrolled = window.scrollY > 0;
},
+ pause () {
+ this.status = this.statuses.paused;
+ this.audioElement.pause();
+ },
+
+ play () {
+ this.status = this.statuses.playing;
+ this.audioElement.play();
+ },
+
+ prevChapter () {
+ if (this.currentChapter > 0) {
+ this.currentChapter--;
+ this.loadChapter(this.currentChapter, 0, true);
+ }
+ },
+
+ setBookmark (silent = true) {
+ if (this.$store.getters.chapters.length == 0) { return; }
+
+ let data = {
+ chapter: this.$store.getters.curChapter,
+ progress: this.currentProgress
+ };
+
+ localStorage.setItem(
+ this.getHash(JSON.stringify(this.$store.getters.chapters)),
+ JSON.stringify(data)
+ );
+
+ if (!silent) { this.showMessage(this.$t("information"), this.$t("bookmarkSaved")); }
+ },
+
showMessage (title, text) {
this.$refs.MessageBox.title = title;
this.$refs.MessageBox.text = text;
this.$refs.MessageBox.dialog = true;
},
+ showSettings () {
+ this.$refs.SettingsDialog.dialog = true;
+ },
+
+ toggleStatus () {
+ if (!this.isChapterLoaded) {
+ this.loadChapter(this.currentChapter || 0);
+ }
+
+ if (!this.isPlaying) {
+ this.play();
+ } else {
+ this.pause();
+ this.setBookmark();
+ }
+ },
+
+ updateProgress () {
+ if (!this.audioElement || !this.audioElement.currentTime) {
+ return this.currentProgress = 0;
+ }
+
+ this.currentProgress = (this.audioElement.currentTime / this.audioElement.duration) * 100;
+
+ let min = parseInt(this.audioElement.currentTime / 60) < 10
+ ? "0" + parseInt(this.audioElement.currentTime / 60)
+ : parseInt(this.audioElement.currentTime / 60);
+ let sec = parseInt(this.audioElement.currentTime % 60) < 10
+ ? "0" + parseInt(this.audioElement.currentTime % 60)
+ : parseInt(this.audioElement.currentTime % 60);
+ this.currentTime = min + ":" + sec;
+ },
+
updateVolume () {
this.audioElement ? this.audioElement.volume = (this.volume / 10) : null;
}
diff --git a/src/components/BookReader.vue b/src/components/BookReader.vue
index 6071549..92cee8a 100644
--- a/src/components/BookReader.vue
+++ b/src/components/BookReader.vue
@@ -123,8 +123,19 @@
data: () => ({
illustrated: false,
- currentChapter: 0
- })
+ }),
+
+ computed: {
+ currentChapter: function () {
+ return this.$store.getters.curChapter;
+ }
+ },
+
+ methods: {
+ loadChapter (index = 0, progress = 0, autoplay = false) {
+ this.$emit("loadChapter", index, progress, autoplay);
+ }
+ }
}
@@ -146,5 +157,5 @@
background: transparent
.v-list-item.no-selection-pl
- padding-left: 74px
+ padding-left: 73px
diff --git a/src/components/SettingsDialog.vue b/src/components/SettingsDialog.vue
new file mode 100644
index 0000000..962b250
--- /dev/null
+++ b/src/components/SettingsDialog.vue
@@ -0,0 +1,60 @@
+
+
+
+
+ {{ $t("settings") }}
+
+
+ {{ $t("playbackRate") }}
+
+
+
+
+ Ok
+
+
+
+
+
+
+
diff --git a/src/plugins/locales/en.js b/src/plugins/locales/en.js
index 27a52be..6fbaf70 100644
--- a/src/plugins/locales/en.js
+++ b/src/plugins/locales/en.js
@@ -2,8 +2,13 @@ const messages = {
appName: "AudioLib",
authors: "Authors",
book: "Books",
+ bookmarkSaved: "Bookmark saved",
books: "Book",
- cycle: "Cycle"
+ cycle: "Cycle",
+ darkTheme: "DarkTheme",
+ information: "Information",
+ playbackRate: "Playback rate",
+ settings: "Settings"
}
export default messages;
diff --git a/src/plugins/locales/ru.js b/src/plugins/locales/ru.js
index c905e1f..e692d08 100644
--- a/src/plugins/locales/ru.js
+++ b/src/plugins/locales/ru.js
@@ -2,8 +2,13 @@ const messages = {
appName: "Аудиотека",
authors: "Авторы",
book: "Книга",
+ bookmarkSaved: "Закладка сохранена",
books: "Книги",
- cycle: "Цикл"
+ cycle: "Цикл",
+ darkTheme: "Тёмная тема",
+ information: "Информация",
+ playbackRate: "Скорость воспроизведения",
+ settings: "Настройки"
}
export default messages;
diff --git a/src/store.js b/src/store.js
index ef6e0cf..2c7767c 100644
--- a/src/store.js
+++ b/src/store.js
@@ -10,7 +10,6 @@ const store = createStore({
wiki: ""
},
authors: [],
- books: [],
book: {
author: "",
photo: "",
@@ -18,24 +17,34 @@ const store = createStore({
cover: "",
wiki: ""
},
- chapters: []
+ books: [],
+ chapters: [],
+ curChapter: 0,
+ darkTheme: false,
+ playbackRate: 1,
}
},
getters: {
- author: state => { return state.author; },
- authors: state => { return state.authors; },
- books: state => { return state.books; },
- book: state => { return state.book; },
- chapters: state => { return state.chapters }
+ author: state => { return state.author; },
+ authors: state => { return state.authors; },
+ book: state => { return state.book; },
+ books: state => { return state.books; },
+ chapters: state => { return state.chapters },
+ curChapter: state => { return state.curChapter },
+ darkTheme: state => { return state.darkTheme },
+ playbackRate: state => { return state.playbackRate },
},
mutations: {
- setAuthors (state, payload) { state.authors = payload; },
- setAuthor (state, payload) { state.author = payload; },
- setBooks (state, payload) { state.books = payload; },
- setBook (state, payload) { state.book = payload; },
- setChapters (state, payload) { state.chapters = payload; }
+ setAuthor (state, payload) { state.author = payload; },
+ setAuthors (state, payload) { state.authors = payload; },
+ setBook (state, payload) { state.book = payload; },
+ setBooks (state, payload) { state.books = payload; },
+ setChapters (state, payload) { state.chapters = payload; },
+ setCurChapter (state, payload) { state.curChapter = payload; },
+ setDarkTheme (state, payload) { state.darkTheme = payload; },
+ setPlaybackRate (state, payload) { state.playbackRate = payload; },
}
});
diff --git a/yarn.lock b/yarn.lock
index 9336a1f..6bf6602 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5942,9 +5942,9 @@ vuetify-loader@^2.0.0-alpha.0:
upath "^2.0.1"
"vuetify@npm:@vuetify/nightly@next":
- version "3.0.0-next-20220415.0"
- resolved "https://registry.yarnpkg.com/@vuetify/nightly/-/nightly-3.0.0-next-20220415.0.tgz#dbcc0ae803a6efbb4eb6dbdb1c9133fcad7012ab"
- integrity sha512-+KByttalgyzdE8JQmbj5sQiKCLcRJgjrdUJL2aY3c8AbLEZEwFa4ryAimkY73W/JFcUTpuw29oGiiN6BV5AFww==
+ version "3.0.0-next-20220416.0"
+ resolved "https://registry.yarnpkg.com/@vuetify/nightly/-/nightly-3.0.0-next-20220416.0.tgz#d3be97e6130900647b51ca924ae60b0812a9b5d2"
+ integrity sha512-3jgtwcZXmrCeR1gcLyL2jvUnZftc9RyaXHdQpXQhmlFazYEtSymsRoD8RAEv2P2GeVGKVKkR7KfBrVhUBEgaSg==
vuex@^4.0.2:
version "4.0.2"