@saooti/octopus-sdk 38.0.32 → 38.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/general.scss +1 -0
- package/src/assets/progressbar.scss +13 -0
- package/src/components/display/podcasts/PodcastInlineList.vue +3 -0
- package/src/components/display/podcasts/PodcastInlineListClassic.vue +3 -0
- package/src/components/display/podcasts/PodcastSwiperList.vue +3 -0
- package/src/components/display/sharing/ShareButtons.vue +1 -1
- package/src/components/misc/ClassicPopover.vue +9 -3
- package/src/components/misc/HomeDropdown.vue +12 -2
- package/src/components/misc/ProgressBar.vue +55 -4
- package/src/components/misc/player/ChapteringModal.vue +95 -0
- package/src/components/misc/player/PlayerChaptering.vue +86 -0
- package/src/components/misc/player/PlayerCompact.vue +3 -0
- package/src/components/misc/player/PlayerComponent.vue +19 -1
- package/src/components/misc/player/PlayerLarge.vue +3 -0
- package/src/components/misc/player/PlayerProgressBar.vue +1 -0
- package/src/components/mixins/player/playerLogicProgress.ts +1 -1
- package/src/components/mixins/player/playerTranscript.ts +39 -8
- package/src/components/pages/Error403Page.vue +21 -1
- package/src/components/pages/RadioPage.vue +2 -1
- package/src/helper/duration.ts +6 -8
- package/src/locale/de.ts +1 -0
- package/src/locale/en.ts +1 -0
- package/src/locale/es.ts +1 -0
- package/src/locale/fr.ts +1 -0
- package/src/locale/it.ts +1 -0
- package/src/locale/sl.ts +1 -0
- package/src/stores/PlayerStore.ts +52 -7
- package/src/stores/class/chaptering/chaptering.ts +23 -0
package/package.json
CHANGED
package/src/assets/general.scss
CHANGED
|
@@ -20,6 +20,19 @@
|
|
|
20
20
|
background-color: $octopus-primary-color;
|
|
21
21
|
transition: width 0.6s ease;
|
|
22
22
|
}
|
|
23
|
+
.octopus-chapter{
|
|
24
|
+
position: absolute;
|
|
25
|
+
background: transparent;
|
|
26
|
+
background-clip: content-box;
|
|
27
|
+
padding: 0 5px;
|
|
28
|
+
box-shadow: inset -2px 1px 0px 0px rgb(0 0 0),
|
|
29
|
+
inset 2px 1px 0px 0px rgb(0 0 0);
|
|
30
|
+
|
|
31
|
+
&:hover{
|
|
32
|
+
background: rgba(255, 255, 255, 0.4);
|
|
33
|
+
box-shadow: -4px 1px 0px 0px rgb(0 0 0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
23
36
|
&,.octopus-progress-bar{
|
|
24
37
|
height: 4px;
|
|
25
38
|
@media (max-width: 960px) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
:no-rubriquage-id="noRubriquageId"
|
|
16
16
|
:query="query"
|
|
17
17
|
:podcast-id="podcastId"
|
|
18
|
+
:last-three-months="lastThreeMonths"
|
|
18
19
|
@update:is-arrow="$emit('update:isArrow', $event)"
|
|
19
20
|
/>
|
|
20
21
|
<PodcastSwiperList
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
:rubriquage-id="rubriquageId"
|
|
33
34
|
:no-rubriquage-id="noRubriquageId"
|
|
34
35
|
:query="query"
|
|
36
|
+
:last-three-months="lastThreeMonths"
|
|
35
37
|
@update:is-arrow="$emit('update:isArrow', $event)"
|
|
36
38
|
/>
|
|
37
39
|
</template>
|
|
@@ -67,6 +69,7 @@ export default defineComponent({
|
|
|
67
69
|
noRubriquageId: { default: () => [], type: Array as () => Array<number> },
|
|
68
70
|
query: { default: undefined, type: String },
|
|
69
71
|
podcastId: { default: undefined, type: Number },
|
|
72
|
+
lastThreeMonths: { default: false, type: Boolean },
|
|
70
73
|
},
|
|
71
74
|
emits: ["update:isArrow"],
|
|
72
75
|
computed: {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
</template>
|
|
48
48
|
|
|
49
49
|
<script lang="ts">
|
|
50
|
+
import dayjs from "dayjs";
|
|
50
51
|
import PodcastInlineListTemplate from "./PodcastInlineListTemplate.vue";
|
|
51
52
|
import octopusApi from "@saooti/octopus-api";
|
|
52
53
|
import domHelper from "../../../helper/dom";
|
|
@@ -84,6 +85,7 @@ export default defineComponent({
|
|
|
84
85
|
noRubriquageId: { default: () => [], type: Array as () => Array<number> },
|
|
85
86
|
query: { default: undefined, type: String },
|
|
86
87
|
podcastId: { default: undefined, type: Number },
|
|
88
|
+
lastThreeMonths: { default: false, type: Boolean },
|
|
87
89
|
},
|
|
88
90
|
emits: ["update:isArrow"],
|
|
89
91
|
|
|
@@ -225,6 +227,7 @@ export default defineComponent({
|
|
|
225
227
|
sort: this.popularSort ? "POPULARITY" : "DATE",
|
|
226
228
|
query: this.query,
|
|
227
229
|
includeStatus: ["READY", "PROCESSING"],
|
|
230
|
+
after: this.popularSort && this.lastThreeMonths ? dayjs().subtract(3, 'months').toISOString(): undefined
|
|
228
231
|
},
|
|
229
232
|
true,
|
|
230
233
|
);
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
</template>
|
|
32
32
|
|
|
33
33
|
<script lang="ts">
|
|
34
|
+
import dayjs from "dayjs";
|
|
34
35
|
import PodcastInlineListTemplate from "./PodcastInlineListTemplate.vue";
|
|
35
36
|
import octopusApi from "@saooti/octopus-api";
|
|
36
37
|
import PodcastItem from "./PodcastItem.vue";
|
|
@@ -64,6 +65,7 @@ export default defineComponent({
|
|
|
64
65
|
rubriquageId: { default: () => [], type: Array as () => Array<number> },
|
|
65
66
|
noRubriquageId: { default: () => [], type: Array as () => Array<number> },
|
|
66
67
|
query: { default: undefined, type: String },
|
|
68
|
+
lastThreeMonths: { default: false, type: Boolean },
|
|
67
69
|
},
|
|
68
70
|
emits: ["update:isArrow"],
|
|
69
71
|
|
|
@@ -129,6 +131,7 @@ export default defineComponent({
|
|
|
129
131
|
sort: this.popularSort ? "POPULARITY" : "DATE",
|
|
130
132
|
query: this.query,
|
|
131
133
|
includeStatus: ["READY", "PROCESSING"],
|
|
134
|
+
after: this.popularSort && this.lastThreeMonths ? dayjs().subtract(3, 'months').toISOString(): undefined
|
|
132
135
|
},
|
|
133
136
|
true,
|
|
134
137
|
);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div class="d-flex align-items-center justify-content-between">
|
|
4
4
|
<div v-if="!isGarStudent && !noSharing" class="d-flex flex-column me-2">
|
|
5
5
|
<div class="h4 mb-2">
|
|
6
|
-
{{ $t("
|
|
6
|
+
{{ $t("Share in one click") }}
|
|
7
7
|
</div>
|
|
8
8
|
<div class="d-flex align-items-center">
|
|
9
9
|
<template v-for="button in arrayShareButtons" :key="button.title">
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
ref="popover"
|
|
6
6
|
tabindex="0"
|
|
7
7
|
class="octopus-popover"
|
|
8
|
-
:class="onlyClick ? 'octopus-dropdown' : ''"
|
|
8
|
+
:class="[onlyClick ? 'octopus-dropdown' : '', popoverClass]"
|
|
9
9
|
:style="positionInlineStyle"
|
|
10
10
|
@blur="clearDataBlur"
|
|
11
11
|
@mouseenter="overPopover=true"
|
|
@@ -30,10 +30,12 @@ export default defineComponent({
|
|
|
30
30
|
target: { type: String, required: true },
|
|
31
31
|
disable: { type: Boolean, default: false },
|
|
32
32
|
onlyClick: { type: Boolean, default: false },
|
|
33
|
+
onlyMouse: { type: Boolean, default: false },
|
|
33
34
|
isFixed: { type: Boolean, default: false },
|
|
34
35
|
relativeClass: { type: String, default: undefined },
|
|
35
36
|
leftPos: { type: Boolean, default: false },
|
|
36
37
|
topPos: { type: Boolean, default: false },
|
|
38
|
+
popoverClass: { type: String, default: undefined },
|
|
37
39
|
},
|
|
38
40
|
data() {
|
|
39
41
|
return {
|
|
@@ -70,7 +72,9 @@ export default defineComponent({
|
|
|
70
72
|
);
|
|
71
73
|
this.targetElement.addEventListener("mouseleave", this.clearDataTimeout);
|
|
72
74
|
}
|
|
73
|
-
|
|
75
|
+
if (!this.onlyMouse) {
|
|
76
|
+
this.targetElement.addEventListener("click", this.setPopoverData);
|
|
77
|
+
}
|
|
74
78
|
this.targetElement.addEventListener("blur", this.clearDataBlur);
|
|
75
79
|
}
|
|
76
80
|
},
|
|
@@ -83,7 +87,9 @@ export default defineComponent({
|
|
|
83
87
|
);
|
|
84
88
|
this.targetElement.removeEventListener("mouseleave", this.clearDataTimeout);
|
|
85
89
|
}
|
|
86
|
-
|
|
90
|
+
if (!this.onlyMouse) {
|
|
91
|
+
this.targetElement.removeEventListener("click", this.setPopoverData);
|
|
92
|
+
}
|
|
87
93
|
this.targetElement.addEventListener("blur", this.clearDataBlur);
|
|
88
94
|
}
|
|
89
95
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
</template>
|
|
61
61
|
</template>
|
|
62
62
|
<hr />
|
|
63
|
-
<a class="octopus-dropdown-item"
|
|
63
|
+
<a class="octopus-dropdown-item c-hand" @click="logoutFunction" >
|
|
64
64
|
{{ $t("Logout") }}
|
|
65
65
|
</a>
|
|
66
66
|
</template>
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
</template>
|
|
73
73
|
|
|
74
74
|
<script lang="ts">
|
|
75
|
+
import crudApi from "@/api/classicCrud";
|
|
75
76
|
import { state } from "../../stores/ParamSdkStore";
|
|
76
77
|
import ClassicPopover from "../misc/ClassicPopover.vue";
|
|
77
78
|
import { useAuthStore } from "@/stores/AuthStore";
|
|
@@ -148,8 +149,17 @@ export default defineComponent({
|
|
|
148
149
|
},
|
|
149
150
|
},
|
|
150
151
|
methods:{
|
|
152
|
+
async logoutFunction(){
|
|
153
|
+
try {
|
|
154
|
+
await crudApi.postData(4, '/logout', undefined);
|
|
155
|
+
await this.$router.push({ path: '/' });
|
|
156
|
+
location.reload();
|
|
157
|
+
} catch (error) {
|
|
158
|
+
//Do nothing
|
|
159
|
+
}
|
|
160
|
+
},
|
|
151
161
|
goToAdministration(){
|
|
152
|
-
if("
|
|
162
|
+
if("backoffice" !== this.$route.name){
|
|
153
163
|
this.$router.push("/main/priv/backoffice");
|
|
154
164
|
}else if (window.history.length > 1) {
|
|
155
165
|
this.$router.go(-1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="octopus-progress">
|
|
2
|
+
<div v-if="!inPlayer || display" id="test-menu-dropdown" class="octopus-progress">
|
|
3
3
|
<div
|
|
4
4
|
v-if="secondaryProgress"
|
|
5
5
|
class="octopus-progress-bar bg-light"
|
|
@@ -48,23 +48,56 @@
|
|
|
48
48
|
/>
|
|
49
49
|
<div
|
|
50
50
|
v-if="isProgressCursor"
|
|
51
|
-
class="progress-bar-cursor"
|
|
51
|
+
class="octopus-progress-bar-cursor"
|
|
52
52
|
:style="'left:' + mainProgress + '%'"
|
|
53
53
|
/>
|
|
54
|
+
<template v-if="playerChapteringPercent">
|
|
55
|
+
<template v-for="chapter in playerChapteringPercent" :key="chapter">
|
|
56
|
+
<div
|
|
57
|
+
:id="'chapter-' + chapter.startPercent"
|
|
58
|
+
class="octopus-progress-bar octopus-chapter"
|
|
59
|
+
role="progressbar"
|
|
60
|
+
aria-valuenow="0"
|
|
61
|
+
aria-valuemin="0"
|
|
62
|
+
aria-valuemax="100"
|
|
63
|
+
:style="{
|
|
64
|
+
left: chapter.startPercent + '%',
|
|
65
|
+
right: 100 - chapter.endPercent + '%',
|
|
66
|
+
}"
|
|
67
|
+
/>
|
|
68
|
+
<Teleport to="#octopus-player-component">
|
|
69
|
+
<ClassicPopover
|
|
70
|
+
:target="'chapter-' + chapter.startPercent"
|
|
71
|
+
:is-fixed="true"
|
|
72
|
+
relative-class="player-container"
|
|
73
|
+
:only-mouse="true"
|
|
74
|
+
popover-class="octopus-small-popover"
|
|
75
|
+
:content="chapter.title"
|
|
76
|
+
/>
|
|
77
|
+
</Teleport>
|
|
78
|
+
</template>
|
|
79
|
+
</template>
|
|
54
80
|
</div>
|
|
55
81
|
</template>
|
|
56
82
|
|
|
57
83
|
<script lang="ts">
|
|
58
84
|
import { usePlayerStore } from "@/stores/PlayerStore";
|
|
59
85
|
import { mapState } from "pinia";
|
|
60
|
-
import { defineComponent } from "vue";
|
|
86
|
+
import { defineAsyncComponent, defineComponent } from "vue";
|
|
87
|
+
const ClassicPopover = defineAsyncComponent(
|
|
88
|
+
() => import("../misc/ClassicPopover.vue"),
|
|
89
|
+
);
|
|
61
90
|
export default defineComponent({
|
|
62
91
|
name: "ProgressBar",
|
|
92
|
+
components: {
|
|
93
|
+
ClassicPopover,
|
|
94
|
+
},
|
|
63
95
|
props: {
|
|
64
96
|
alertBar: { default: undefined, type: Number },
|
|
65
97
|
mainProgress: { default: 0, type: Number },
|
|
66
98
|
secondaryProgress: { default: 0, type: Number },
|
|
67
99
|
isProgressCursor: { default: false, type: Boolean },
|
|
100
|
+
inPlayer: { default: false, type: Boolean },
|
|
68
101
|
},
|
|
69
102
|
data() {
|
|
70
103
|
return {
|
|
@@ -73,7 +106,14 @@ export default defineComponent({
|
|
|
73
106
|
};
|
|
74
107
|
},
|
|
75
108
|
computed: {
|
|
76
|
-
...mapState(usePlayerStore, [
|
|
109
|
+
...mapState(usePlayerStore, [
|
|
110
|
+
"playerMedia",
|
|
111
|
+
"playerChapteringPercent",
|
|
112
|
+
"playerStatus",
|
|
113
|
+
]),
|
|
114
|
+
display() {
|
|
115
|
+
return "STOPPED" !== this.playerStatus;
|
|
116
|
+
},
|
|
77
117
|
},
|
|
78
118
|
watch: {
|
|
79
119
|
playerMedia: {
|
|
@@ -104,4 +144,15 @@ export default defineComponent({
|
|
|
104
144
|
|
|
105
145
|
<style lang="scss">
|
|
106
146
|
@import "../../assets/progressbar.scss";
|
|
147
|
+
.octopus-app .player-container {
|
|
148
|
+
.octopus-small-popover {
|
|
149
|
+
font-size: 0.7rem;
|
|
150
|
+
background: #282828;
|
|
151
|
+
color: white;
|
|
152
|
+
border: 0;
|
|
153
|
+
.p-2 {
|
|
154
|
+
padding: 0.2rem !important;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
107
158
|
</style>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ClassicModal
|
|
3
|
+
id-modal="chaptering-modal"
|
|
4
|
+
:title-modal="$t('Chaptering')"
|
|
5
|
+
@close="closePopup"
|
|
6
|
+
>
|
|
7
|
+
<template #body>
|
|
8
|
+
<div class="d-flex flex-column">
|
|
9
|
+
<button
|
|
10
|
+
v-for="(chapter, index) in playerChapteringPercent"
|
|
11
|
+
:key="chapter"
|
|
12
|
+
class="btn d-flex flex-nowrap align-items-center p-2 mt-1 c-hand text-truncate mb-1"
|
|
13
|
+
:class="actualChapter === index ? 'chapter-selected':'border'"
|
|
14
|
+
@click="goToChapter(index)"
|
|
15
|
+
>
|
|
16
|
+
<div class="d-flex align-items-center me-auto">
|
|
17
|
+
<svg v-if="actualChapter === index" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-soundwave" viewBox="0 0 16 16">
|
|
18
|
+
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5m-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5m12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5"/>
|
|
19
|
+
</svg>
|
|
20
|
+
<div v-else>{{ index + 1 }}</div>
|
|
21
|
+
<div class="ms-2">{{ "- " + chapter.title }}</div>
|
|
22
|
+
</div>
|
|
23
|
+
<div>{{ chapter.startDisplay }}</div>
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
<template #footer>
|
|
28
|
+
<button class="btn m-1" @click="closePopup">
|
|
29
|
+
{{ $t("Close") }}
|
|
30
|
+
</button>
|
|
31
|
+
</template>
|
|
32
|
+
</ClassicModal>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import { usePlayerStore } from "@/stores/PlayerStore";
|
|
37
|
+
import { mapState, mapActions } from "pinia";
|
|
38
|
+
import ClassicModal from "../modal/ClassicModal.vue";
|
|
39
|
+
import { defineComponent } from "vue";
|
|
40
|
+
export default defineComponent({
|
|
41
|
+
name: "ChapteringModal",
|
|
42
|
+
components: {
|
|
43
|
+
ClassicModal,
|
|
44
|
+
},
|
|
45
|
+
props: {actualChapter: {default: -1, type: Number}},
|
|
46
|
+
emits: ["close"],
|
|
47
|
+
data() {
|
|
48
|
+
return {
|
|
49
|
+
audioPlayer: null as HTMLAudioElement | null,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
computed: {
|
|
53
|
+
...mapState(usePlayerStore, [
|
|
54
|
+
"playerPodcast",
|
|
55
|
+
"playerLive",
|
|
56
|
+
"playerChapteringPercent",
|
|
57
|
+
"playerTotal",
|
|
58
|
+
"playerElapsed",
|
|
59
|
+
]),
|
|
60
|
+
},
|
|
61
|
+
created() {
|
|
62
|
+
this.audioPlayer = document.querySelector("#audio-player");
|
|
63
|
+
},
|
|
64
|
+
methods: {
|
|
65
|
+
...mapActions(usePlayerStore, [
|
|
66
|
+
"playerUpdateSeekTime",
|
|
67
|
+
"playerUpdateElapsed",
|
|
68
|
+
]),
|
|
69
|
+
closePopup(): void {
|
|
70
|
+
this.$emit("close");
|
|
71
|
+
},
|
|
72
|
+
goToChapter(index: number) {
|
|
73
|
+
if (!this.playerChapteringPercent || !this.audioPlayer) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const seekTime =
|
|
77
|
+
this.playerTotal *
|
|
78
|
+
(this.playerChapteringPercent[index].startPercent / 100);
|
|
79
|
+
this.playerUpdateSeekTime(seekTime);
|
|
80
|
+
if (0 === seekTime) {
|
|
81
|
+
this.playerUpdateElapsed(0);
|
|
82
|
+
}
|
|
83
|
+
this.audioPlayer.currentTime = seekTime;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
</script>
|
|
88
|
+
<style lang="scss">
|
|
89
|
+
@import "@scss/_variables.scss";
|
|
90
|
+
.octopus-app {
|
|
91
|
+
.chapter-selected{
|
|
92
|
+
border: $octopus-primary-color 3px solid;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
</style>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="actualChapter" class="d-flex mb-1">
|
|
3
|
+
<button
|
|
4
|
+
class="btn-transparent d-flex align-items-center text-truncate medium-text text-light"
|
|
5
|
+
@click="showChaptering = !showChaptering"
|
|
6
|
+
>
|
|
7
|
+
<div class="text-truncate">
|
|
8
|
+
{{ actualIndex + 1 + " - " + actualChapter.title }}
|
|
9
|
+
</div>
|
|
10
|
+
<span class="saooti-right small-text" />
|
|
11
|
+
</button>
|
|
12
|
+
<ChapteringModal v-if="showChaptering" @close="showChaptering = false" :actual-chapter="actualIndex" />
|
|
13
|
+
</div>
|
|
14
|
+
<div v-else-if="playerChapteringPercent" class="margin-chaptering"></div>
|
|
15
|
+
</template>
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import { ChapterPercent } from "@/stores/class/chaptering/chaptering";
|
|
18
|
+
import { usePlayerStore } from "@/stores/PlayerStore";
|
|
19
|
+
import { mapState } from "pinia";
|
|
20
|
+
import { defineAsyncComponent, defineComponent } from "vue";
|
|
21
|
+
const ChapteringModal = defineAsyncComponent(
|
|
22
|
+
() => import("./ChapteringModal.vue"),
|
|
23
|
+
);
|
|
24
|
+
export default defineComponent({
|
|
25
|
+
name: "PlayerChaptering",
|
|
26
|
+
|
|
27
|
+
components: {
|
|
28
|
+
ChapteringModal,
|
|
29
|
+
},
|
|
30
|
+
data() {
|
|
31
|
+
return {
|
|
32
|
+
actualChapter: undefined as ChapterPercent | undefined,
|
|
33
|
+
actualIndex: -1 as number,
|
|
34
|
+
showChaptering: false as boolean,
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
computed: {
|
|
38
|
+
...mapState(usePlayerStore, ["playerChapteringPercent", "playerElapsed"]),
|
|
39
|
+
},
|
|
40
|
+
watch: {
|
|
41
|
+
playerElapsed: {
|
|
42
|
+
immediate: true,
|
|
43
|
+
handler() {
|
|
44
|
+
if (!this.playerChapteringPercent) {
|
|
45
|
+
this.actualChapter = undefined;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const progressPercent = (this.playerElapsed ?? 0) * 100;
|
|
49
|
+
if (
|
|
50
|
+
this.actualChapter &&
|
|
51
|
+
this.isInChapter(progressPercent, this.actualChapter)
|
|
52
|
+
) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (
|
|
56
|
+
let i = 0, len = this.playerChapteringPercent.length;
|
|
57
|
+
i < len;
|
|
58
|
+
i++
|
|
59
|
+
) {
|
|
60
|
+
if (
|
|
61
|
+
this.isInChapter(progressPercent, this.playerChapteringPercent[i])
|
|
62
|
+
) {
|
|
63
|
+
this.actualChapter = this.playerChapteringPercent[i];
|
|
64
|
+
this.actualIndex = i;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
this.actualChapter = undefined;
|
|
69
|
+
this.actualIndex = -1;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
methods: {
|
|
74
|
+
isInChapter(val: number, chapter: ChapterPercent) {
|
|
75
|
+
return Math.floor(chapter.startPercent) <= val && val < Math.floor(chapter.endPercent);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
<style lang="scss">
|
|
81
|
+
.octopus-app {
|
|
82
|
+
.margin-chaptering{
|
|
83
|
+
height: 23px;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
</style>
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
{{ playedTime }} / {{ totalTime }}
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
|
+
<PlayerChaptering />
|
|
37
38
|
<PlayerProgressBar
|
|
38
39
|
v-if="!radioUrl"
|
|
39
40
|
:show-timeline="showTimeline"
|
|
@@ -69,6 +70,7 @@ import { playerDisplay } from "../../mixins/player/playerDisplay";
|
|
|
69
70
|
import imageProxy from "../../mixins/imageProxy";
|
|
70
71
|
import ClassicSpinner from "../ClassicSpinner.vue";
|
|
71
72
|
import PlayerTimeline from "./PlayerTimeline.vue";
|
|
73
|
+
import PlayerChaptering from "./PlayerChaptering.vue";
|
|
72
74
|
import { defineAsyncComponent, defineComponent } from "vue";
|
|
73
75
|
const RadioProgressBar = defineAsyncComponent(
|
|
74
76
|
() => import("./radio/RadioProgressBar.vue"),
|
|
@@ -84,6 +86,7 @@ export default defineComponent({
|
|
|
84
86
|
RadioProgressBar,
|
|
85
87
|
PlayerTimeline,
|
|
86
88
|
ClassicSpinner,
|
|
89
|
+
PlayerChaptering,
|
|
87
90
|
},
|
|
88
91
|
mixins: [playerDisplay, imageProxy],
|
|
89
92
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
+
id="octopus-player-component"
|
|
3
4
|
class="player-container"
|
|
4
5
|
:class="playerVideo ? 'player-video' : ''"
|
|
5
6
|
:style="{ height: playerHeight }"
|
|
6
7
|
@transitionend="onHidden"
|
|
7
8
|
>
|
|
8
|
-
<template v-if="
|
|
9
|
+
<template v-if="displayWithTimeout">
|
|
9
10
|
<PlayerVideo v-if="playerVideo" />
|
|
10
11
|
<template v-else>
|
|
11
12
|
<audio
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
@error="onError"
|
|
20
21
|
@seeked="onSeeked"
|
|
21
22
|
@pause="onPause"
|
|
23
|
+
@loadedmetadata="checkDelaytWithStitching"
|
|
22
24
|
/>
|
|
23
25
|
<PlayerCompact
|
|
24
26
|
v-if="!playerLargeVersion"
|
|
@@ -86,6 +88,7 @@ export default defineComponent({
|
|
|
86
88
|
comments: [] as Array<CommentPodcast>,
|
|
87
89
|
audioUrlToPlay: "" as string,
|
|
88
90
|
hlsReady: false as boolean,
|
|
91
|
+
displayWithTimeout: false as boolean,
|
|
89
92
|
};
|
|
90
93
|
},
|
|
91
94
|
computed: {
|
|
@@ -104,6 +107,15 @@ export default defineComponent({
|
|
|
104
107
|
playerHeight(): void {
|
|
105
108
|
this.$emit("hide", 0 === this.playerHeight);
|
|
106
109
|
},
|
|
110
|
+
display(): void {
|
|
111
|
+
if (this.display) {
|
|
112
|
+
this.displayWithTimeout = this.display;
|
|
113
|
+
} else {
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
this.displayWithTimeout = this.display;
|
|
116
|
+
}, 3000);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
107
119
|
},
|
|
108
120
|
|
|
109
121
|
methods: {
|
|
@@ -141,6 +153,12 @@ export default defineComponent({
|
|
|
141
153
|
transition: height 1s;
|
|
142
154
|
background: #282828 !important;
|
|
143
155
|
font-size: 1rem;
|
|
156
|
+
.medium-text {
|
|
157
|
+
font-size: 0.65rem;
|
|
158
|
+
}
|
|
159
|
+
.small-text {
|
|
160
|
+
font-size: 0.5rem;
|
|
161
|
+
}
|
|
144
162
|
|
|
145
163
|
@media (max-width: 960px) {
|
|
146
164
|
.d-flex {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
{{ podcastTitle }}
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
|
+
<PlayerChaptering class="justify-content-center w-100" />
|
|
27
28
|
<div class="player-grow-large-content">
|
|
28
29
|
<PlayerProgressBar
|
|
29
30
|
v-if="!radioUrl"
|
|
@@ -86,6 +87,7 @@ import ClassicSpinner from "../ClassicSpinner.vue";
|
|
|
86
87
|
import { playerDisplay } from "../../mixins/player/playerDisplay";
|
|
87
88
|
import imageProxy from "../../mixins/imageProxy";
|
|
88
89
|
import PlayerTimeline from "./PlayerTimeline.vue";
|
|
90
|
+
import PlayerChaptering from "./PlayerChaptering.vue";
|
|
89
91
|
import { defineAsyncComponent, defineComponent } from "vue";
|
|
90
92
|
import { CommentPodcast } from "@/stores/class/general/comment";
|
|
91
93
|
const RadioProgressBar = defineAsyncComponent(
|
|
@@ -106,6 +108,7 @@ export default defineComponent({
|
|
|
106
108
|
PlayerTimeline,
|
|
107
109
|
ClassicSpinner,
|
|
108
110
|
RadioHistory,
|
|
111
|
+
PlayerChaptering,
|
|
109
112
|
},
|
|
110
113
|
mixins: [playerDisplay, imageProxy],
|
|
111
114
|
|
|
@@ -2,12 +2,33 @@ import octopusApi from "@saooti/octopus-api";
|
|
|
2
2
|
import { defineComponent } from "vue";
|
|
3
3
|
import { usePlayerStore } from "@/stores/PlayerStore";
|
|
4
4
|
import { mapState, mapActions } from "pinia";
|
|
5
|
+
import { AdserverOtherEmission } from "@/stores/class/adserver/adserverOtherEmission";
|
|
5
6
|
export const playerTranscript = defineComponent({
|
|
6
7
|
computed: {
|
|
7
|
-
...mapState(usePlayerStore, ["playerTranscript", "playerPodcast"]),
|
|
8
|
+
...mapState(usePlayerStore, ["playerTranscript", "playerPodcast", 'playerDelayStitching']),
|
|
8
9
|
},
|
|
9
10
|
methods: {
|
|
10
|
-
...mapActions(usePlayerStore, ["playerUpdateTranscript"]),
|
|
11
|
+
...mapActions(usePlayerStore, ["playerUpdateTranscript", "playerUpdateDelayStitching"]),
|
|
12
|
+
async checkDelaytWithStitching(){
|
|
13
|
+
this.playerUpdateDelayStitching(0);
|
|
14
|
+
const audioPlayer = document.querySelector("#audio-player") as HTMLAudioElement;
|
|
15
|
+
if (!this.playerTranscript || !audioPlayer || !this.playerPodcast ||
|
|
16
|
+
audioPlayer.duration <= this.playerPodcast.duration / 1000 + 5)
|
|
17
|
+
{
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let adserverConfig = await octopusApi.fetchDataPublic<AdserverOtherEmission>(0,`ad/test/podcast/${this.playerPodcast.podcastId}`);
|
|
21
|
+
const doubletsLength = adserverConfig.config.doublets.length;
|
|
22
|
+
if(1=== doubletsLength && "pre" === adserverConfig.config.doublets[0].timing.insertion){
|
|
23
|
+
this.playerUpdateDelayStitching( audioPlayer.duration - (this.playerPodcast.duration / 1000));
|
|
24
|
+
}else if(0===doubletsLength || 1=== doubletsLength && "post" === adserverConfig.config.doublets[0].timing.insertion){
|
|
25
|
+
return;
|
|
26
|
+
}else{
|
|
27
|
+
// todo remove chaptering
|
|
28
|
+
this.playerUpdateChaptering();
|
|
29
|
+
this.playerUpdateTranscript();
|
|
30
|
+
}
|
|
31
|
+
},
|
|
11
32
|
async getTranscription(): Promise<void> {
|
|
12
33
|
if (!this.playerPodcast) {
|
|
13
34
|
this.playerUpdateTranscript();
|
|
@@ -20,10 +41,13 @@ export const playerTranscript = defineComponent({
|
|
|
20
41
|
const arrayTranscript = this.parseSrt(result);
|
|
21
42
|
const actualText =
|
|
22
43
|
arrayTranscript?.[0]?.startTime === 0 ? arrayTranscript[0].text : "";
|
|
44
|
+
if(!arrayTranscript){
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
23
47
|
this.playerUpdateTranscript({
|
|
24
48
|
actual: 0,
|
|
25
49
|
actualText: actualText,
|
|
26
|
-
value: arrayTranscript
|
|
50
|
+
value: arrayTranscript
|
|
27
51
|
});
|
|
28
52
|
},
|
|
29
53
|
parseSrt(transcript: string) {
|
|
@@ -52,23 +76,30 @@ export const playerTranscript = defineComponent({
|
|
|
52
76
|
return +a[0] * 60 * 60 + +a[1] * 60 + +parseFloat(a[2]);
|
|
53
77
|
},
|
|
54
78
|
onTimeUpdateTranscript(currentTime: number) {
|
|
79
|
+
if(!this.playerTranscript || !this.playerTranscript.value.length){
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const startTime = (this.playerTranscript.value[this.playerTranscript.actual]?.startTime ?? 0) + this.playerDelayStitching;
|
|
83
|
+
if (startTime <= currentTime) {
|
|
84
|
+
this.playerTranscript.actualText = this.playerTranscript.value[this.playerTranscript?.actual]?.text ??"";
|
|
85
|
+
}
|
|
55
86
|
if (
|
|
56
|
-
this.playerTranscript
|
|
57
|
-
|
|
58
|
-
Infinity) < currentTime
|
|
87
|
+
(this.playerTranscript.value[this.playerTranscript.actual]?.endTime ??
|
|
88
|
+
Infinity) + this.playerDelayStitching < currentTime
|
|
59
89
|
) {
|
|
60
90
|
this.playerTranscript.actual += 1;
|
|
61
91
|
this.playerTranscript.actualText =
|
|
62
|
-
this.playerTranscript?.value[this.playerTranscript
|
|
92
|
+
this.playerTranscript?.value[this.playerTranscript.actual]?.text ??
|
|
63
93
|
"";
|
|
64
94
|
}
|
|
65
95
|
},
|
|
96
|
+
|
|
66
97
|
onSeekedTranscript(currentTime: number) {
|
|
67
98
|
if (this.playerTranscript) {
|
|
68
99
|
let newActual = 0;
|
|
69
100
|
while (
|
|
70
101
|
currentTime >
|
|
71
|
-
(this.playerTranscript.value[newActual]?.endTime ?? Infinity)
|
|
102
|
+
(this.playerTranscript.value[newActual]?.endTime ?? Infinity) + this.playerDelayStitching
|
|
72
103
|
) {
|
|
73
104
|
newActual += 1;
|
|
74
105
|
}
|
|
@@ -20,15 +20,24 @@
|
|
|
20
20
|
/>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
|
+
<button
|
|
24
|
+
v-if="authenticated"
|
|
25
|
+
class="btn btn-primary"
|
|
26
|
+
@click="logoutFunction"
|
|
27
|
+
|
|
28
|
+
>{{ authText }}</button
|
|
29
|
+
>
|
|
23
30
|
<a
|
|
31
|
+
v-else
|
|
24
32
|
class="btn btn-primary"
|
|
25
|
-
|
|
33
|
+
href="/sso/login"
|
|
26
34
|
>{{ authText }}</a
|
|
27
35
|
>
|
|
28
36
|
</div>
|
|
29
37
|
</template>
|
|
30
38
|
|
|
31
39
|
<script lang="ts">
|
|
40
|
+
import crudApi from "@/api/classicCrud";
|
|
32
41
|
import { state } from "../../stores/ParamSdkStore";
|
|
33
42
|
import { useGeneralStore } from "@/stores/GeneralStore";
|
|
34
43
|
import { mapState } from "pinia";
|
|
@@ -47,6 +56,17 @@ export default defineComponent({
|
|
|
47
56
|
mounted() {
|
|
48
57
|
document.title = this.metaTitle;
|
|
49
58
|
},
|
|
59
|
+
methods:{
|
|
60
|
+
async logoutFunction(){
|
|
61
|
+
try {
|
|
62
|
+
await crudApi.postData(4, '/logout', undefined);
|
|
63
|
+
await this.$router.push({ path: '/' });
|
|
64
|
+
location.reload();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
//Do nothing
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
50
70
|
});
|
|
51
71
|
</script>
|
|
52
72
|
<style lang="scss">
|
|
@@ -91,7 +91,8 @@ export default defineComponent({
|
|
|
91
91
|
computed: {
|
|
92
92
|
editRight(): boolean {
|
|
93
93
|
return (
|
|
94
|
-
(true === this.authenticated &&
|
|
94
|
+
(true === this.authenticated &&
|
|
95
|
+
true === state.generalParameters.isRadio &&
|
|
95
96
|
this.myOrganisationId === this.radio?.organisationId) ||
|
|
96
97
|
true === state.generalParameters.isAdmin
|
|
97
98
|
);
|
package/src/helper/duration.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
convertTimestamptoSeconds(timestamp: string){
|
|
3
|
+
const [hours, minutes, seconds] = timestamp.split(':');
|
|
4
|
+
return Number(hours) * 60 * 60 + Number(minutes) * 60 + Number(seconds);
|
|
5
|
+
},
|
|
2
6
|
formatToString(value: number) {
|
|
3
7
|
if (value < 10) {
|
|
4
8
|
return "0" + value;
|
|
5
9
|
}
|
|
6
10
|
return value.toString();
|
|
7
11
|
},
|
|
8
|
-
formatDuration(totalSeconds: number): string {
|
|
12
|
+
formatDuration(totalSeconds: number, separator="'", isLast=true): string {
|
|
9
13
|
const hours = Math.floor(totalSeconds / 3600);
|
|
10
14
|
const minutes = Math.floor((totalSeconds - hours * 3600) / 60);
|
|
11
15
|
const seconds = totalSeconds - hours * 3600 - minutes * 60;
|
|
12
|
-
return (
|
|
13
|
-
(hours > 0 ? this.formatToString(hours) + "'" : "") +
|
|
14
|
-
this.formatToString(minutes) +
|
|
15
|
-
"'" +
|
|
16
|
-
this.formatToString(seconds) +
|
|
17
|
-
"''"
|
|
18
|
-
);
|
|
16
|
+
return (hours > 0? this.formatToString(hours)+separator:"") + this.formatToString(minutes) +separator+ this.formatToString(seconds) + (isLast?separator:'' );
|
|
19
17
|
},
|
|
20
18
|
};
|
package/src/locale/de.ts
CHANGED
package/src/locale/en.ts
CHANGED
package/src/locale/es.ts
CHANGED
package/src/locale/fr.ts
CHANGED
package/src/locale/it.ts
CHANGED
package/src/locale/sl.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { Media } from "@/stores/class/general/media";
|
|
|
3
3
|
import { MediaRadio, Radio } from "@/stores/class/general/player";
|
|
4
4
|
import { Podcast } from "@/stores/class/general/podcast";
|
|
5
5
|
import { defineStore } from "pinia";
|
|
6
|
+
import { Chaptering, ChapteringPercent } from "./class/chaptering/chaptering";
|
|
7
|
+
import octopusApi from "@saooti/octopus-api";
|
|
6
8
|
interface Transcript {
|
|
7
9
|
actual: number;
|
|
8
10
|
actualText: string;
|
|
@@ -22,6 +24,8 @@ interface PlayerState {
|
|
|
22
24
|
playerTranscript?: Transcript;
|
|
23
25
|
playerLargeVersion: boolean;
|
|
24
26
|
playerVideo: boolean;
|
|
27
|
+
playerChaptering?: Chaptering;
|
|
28
|
+
playerDelayStitching: number;
|
|
25
29
|
}
|
|
26
30
|
export const usePlayerStore = defineStore("PlayerStore", {
|
|
27
31
|
state: (): PlayerState => ({
|
|
@@ -36,13 +40,35 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
36
40
|
playerSeekTime: 0,
|
|
37
41
|
playerLargeVersion: false,
|
|
38
42
|
playerVideo: false,
|
|
43
|
+
playerChaptering: undefined,
|
|
44
|
+
playerDelayStitching:0,
|
|
39
45
|
}),
|
|
40
46
|
getters: {
|
|
47
|
+
playerChapteringPercent(): ChapteringPercent|undefined{
|
|
48
|
+
if(!this.playerChaptering || 0===this.playerTotal){
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let chapteringPercent: ChapteringPercent = [];
|
|
52
|
+
for (let i = 0, len = this.playerChaptering.chapters.length; i < len; i++) {
|
|
53
|
+
const startTimeWithDelay = this.playerChaptering.chapters[i].startTime + Math.floor(this.playerDelayStitching);
|
|
54
|
+
chapteringPercent.push({
|
|
55
|
+
startTime : startTimeWithDelay,
|
|
56
|
+
startDisplay: DurationHelper.formatDuration(startTimeWithDelay, ':', false),
|
|
57
|
+
startPercent: (startTimeWithDelay * 100 ) / (Math.round(this.playerTotal)),
|
|
58
|
+
endPercent:100,
|
|
59
|
+
title: this.playerChaptering.chapters[i].title
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
for (let i = 0, len = chapteringPercent.length; i < len; i++) {
|
|
63
|
+
chapteringPercent[i].endPercent = chapteringPercent[i].startPercent + ((chapteringPercent[i+1]?.startPercent ?? 100) - chapteringPercent[i].startPercent);
|
|
64
|
+
}
|
|
65
|
+
return chapteringPercent;
|
|
66
|
+
},
|
|
41
67
|
playerHeight() {
|
|
42
68
|
if ("STOPPED" === this.playerStatus) return 0;
|
|
43
69
|
if (this.playerVideo) return "0px"/* "281px" */;
|
|
44
70
|
if (this.playerLargeVersion) return "27rem";
|
|
45
|
-
if (window.innerWidth > 450) return "
|
|
71
|
+
if (window.innerWidth > 450) return "6rem";
|
|
46
72
|
return "3.5rem";
|
|
47
73
|
},
|
|
48
74
|
playedTime(): string {
|
|
@@ -91,7 +117,7 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
91
117
|
},
|
|
92
118
|
},
|
|
93
119
|
actions: {
|
|
94
|
-
playerPlay(param?: any, isVideo = false) {
|
|
120
|
+
async playerPlay(param?: any, isVideo = false) {
|
|
95
121
|
if (!param) {
|
|
96
122
|
this.playerStatus = "STOPPED";
|
|
97
123
|
this.playerPodcast = undefined;
|
|
@@ -100,6 +126,7 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
100
126
|
this.playerRadio = undefined;
|
|
101
127
|
this.playerElapsed = 0;
|
|
102
128
|
this.playerVideo = false;
|
|
129
|
+
this.playerChaptering=undefined;
|
|
103
130
|
return;
|
|
104
131
|
}
|
|
105
132
|
if (
|
|
@@ -118,16 +145,26 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
118
145
|
this.playerRadio = undefined;
|
|
119
146
|
this.playerVideo = isVideo;
|
|
120
147
|
this.playerElapsed = 0;
|
|
148
|
+
this.playerChaptering=undefined;
|
|
121
149
|
if (
|
|
122
150
|
param.conferenceId &&
|
|
123
151
|
(!param.podcastId || param.processingStatus !== "READY")
|
|
124
152
|
) {
|
|
125
153
|
this.playerLive = param;
|
|
126
|
-
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (param.podcastId) {
|
|
127
157
|
this.playerPodcast = param;
|
|
128
|
-
|
|
158
|
+
if(param.annotations?.chaptering){
|
|
159
|
+
this.playerChaptering = await octopusApi.fetchDataPublic<Chaptering>(4, (param.annotations.chaptering as string));
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (param.mediaId) {
|
|
129
164
|
this.playerMedia = param;
|
|
130
|
-
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (param.canalId) {
|
|
131
168
|
this.playerRadio = { ...param, ...{ isInit: false } };
|
|
132
169
|
}
|
|
133
170
|
},
|
|
@@ -158,16 +195,24 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
158
195
|
this.playerRadio.podcast = podcast;
|
|
159
196
|
},
|
|
160
197
|
|
|
161
|
-
playerUpdateElapsed(elapsed: number, total
|
|
198
|
+
playerUpdateElapsed(elapsed: number, total?: number) {
|
|
162
199
|
this.playerElapsed = elapsed;
|
|
163
|
-
|
|
200
|
+
if(total){
|
|
201
|
+
this.playerTotal = total;
|
|
202
|
+
}
|
|
164
203
|
},
|
|
165
204
|
|
|
166
205
|
playerUpdateTranscript(transcript?: Transcript) {
|
|
167
206
|
this.playerTranscript = transcript;
|
|
168
207
|
},
|
|
208
|
+
playerUpdateDelayStitching(delay: number){
|
|
209
|
+
this.playerDelayStitching = delay;
|
|
210
|
+
},
|
|
169
211
|
playerUpdateLargeVersion(largeVersion: boolean) {
|
|
170
212
|
this.playerLargeVersion = largeVersion;
|
|
171
213
|
},
|
|
214
|
+
playerUpdateChaptering(chaptering?: Chaptering){
|
|
215
|
+
this.playerChaptering = chaptering;
|
|
216
|
+
}
|
|
172
217
|
},
|
|
173
218
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//https://github.com/Podcastindex-org/podcast-namespace/blob/main/chapters/jsonChapters.md
|
|
2
|
+
export interface Chaptering {
|
|
3
|
+
version: string;
|
|
4
|
+
chapters: Array<Chapter>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Chapter{
|
|
8
|
+
startTime: number;
|
|
9
|
+
title: string;
|
|
10
|
+
img?: string;
|
|
11
|
+
url?:string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ChapterPercent {
|
|
15
|
+
startTime: number;
|
|
16
|
+
startDisplay: string;
|
|
17
|
+
startPercent: number;
|
|
18
|
+
endPercent: number;
|
|
19
|
+
title: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export type ChapteringPercent = Array<ChapterPercent>;
|