@saooti/octopus-sdk 38.1.0 → 38.1.1
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/sharing/ShareButtonsIntern.vue +2 -2
- package/src/components/misc/ClassicPopover.vue +9 -3
- package/src/components/misc/ProgressBar.vue +54 -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 +18 -1
- package/src/components/misc/player/PlayerLarge.vue +3 -0
- package/src/components/mixins/player/playerLogicProgress.ts +1 -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/ParamSdkStore.ts +11 -11
- package/src/stores/PlayerStore.ts +43 -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) {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<div :class="button.icon" />
|
|
19
19
|
</a>
|
|
20
20
|
</template>
|
|
21
|
-
<router-link
|
|
21
|
+
<!-- <router-link
|
|
22
22
|
v-if="!isPodcastmaker && authenticated && podcast && isProduction"
|
|
23
23
|
:class="getClass('saooti-share')"
|
|
24
24
|
:title="$t('Advanced sharing')"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
name: 'advancedShare',
|
|
27
27
|
params: { podcastId: podcast.podcastId },
|
|
28
28
|
}"
|
|
29
|
-
/>
|
|
29
|
+
/> -->
|
|
30
30
|
</div>
|
|
31
31
|
</div>
|
|
32
32
|
<div v-if="podcast || emission || playlist" class="d-flex flex-column me-2">
|
|
@@ -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
|
>
|
|
@@ -28,10 +28,12 @@ export default defineComponent({
|
|
|
28
28
|
target: { type: String, required: true },
|
|
29
29
|
disable: { type: Boolean, default: false },
|
|
30
30
|
onlyClick: { type: Boolean, default: false },
|
|
31
|
+
onlyMouse: { type: Boolean, default: false },
|
|
31
32
|
isFixed: { type: Boolean, default: false },
|
|
32
33
|
relativeClass: { type: String, default: undefined },
|
|
33
34
|
leftPos: { type: Boolean, default: false },
|
|
34
35
|
topPos: { type: Boolean, default: false },
|
|
36
|
+
popoverClass: { type: String, default: undefined },
|
|
35
37
|
},
|
|
36
38
|
data() {
|
|
37
39
|
return {
|
|
@@ -67,7 +69,9 @@ export default defineComponent({
|
|
|
67
69
|
);
|
|
68
70
|
this.targetElement.addEventListener("mouseleave", this.clearData);
|
|
69
71
|
}
|
|
70
|
-
|
|
72
|
+
if (!this.onlyMouse) {
|
|
73
|
+
this.targetElement.addEventListener("click", this.setPopoverData);
|
|
74
|
+
}
|
|
71
75
|
this.targetElement.addEventListener("blur", this.clearDataBlur);
|
|
72
76
|
}
|
|
73
77
|
},
|
|
@@ -80,7 +84,9 @@ export default defineComponent({
|
|
|
80
84
|
);
|
|
81
85
|
this.targetElement.removeEventListener("mouseleave", this.clearData);
|
|
82
86
|
}
|
|
83
|
-
|
|
87
|
+
if (!this.onlyMouse) {
|
|
88
|
+
this.targetElement.removeEventListener("click", this.setPopoverData);
|
|
89
|
+
}
|
|
84
90
|
this.targetElement.addEventListener("blur", this.clearDataBlur);
|
|
85
91
|
}
|
|
86
92
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="octopus-progress">
|
|
2
|
+
<div v-if="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,18 +48,50 @@
|
|
|
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 },
|
|
@@ -73,7 +105,14 @@ export default defineComponent({
|
|
|
73
105
|
};
|
|
74
106
|
},
|
|
75
107
|
computed: {
|
|
76
|
-
...mapState(usePlayerStore, [
|
|
108
|
+
...mapState(usePlayerStore, [
|
|
109
|
+
"playerMedia",
|
|
110
|
+
"playerChapteringPercent",
|
|
111
|
+
"playerStatus",
|
|
112
|
+
]),
|
|
113
|
+
display() {
|
|
114
|
+
return "STOPPED" !== this.playerStatus;
|
|
115
|
+
},
|
|
77
116
|
},
|
|
78
117
|
watch: {
|
|
79
118
|
playerMedia: {
|
|
@@ -104,4 +143,15 @@ export default defineComponent({
|
|
|
104
143
|
|
|
105
144
|
<style lang="scss">
|
|
106
145
|
@import "../../assets/progressbar.scss";
|
|
146
|
+
.octopus-app .player-container {
|
|
147
|
+
.octopus-small-popover {
|
|
148
|
+
font-size: 0.7rem;
|
|
149
|
+
background: #282828;
|
|
150
|
+
color: white;
|
|
151
|
+
border: 0;
|
|
152
|
+
.p-2 {
|
|
153
|
+
padding: 0.2rem !important;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
107
157
|
</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
|
|
@@ -86,6 +87,7 @@ export default defineComponent({
|
|
|
86
87
|
comments: [] as Array<CommentPodcast>,
|
|
87
88
|
audioUrlToPlay: "" as string,
|
|
88
89
|
hlsReady: false as boolean,
|
|
90
|
+
displayWithTimeout: false as boolean,
|
|
89
91
|
};
|
|
90
92
|
},
|
|
91
93
|
computed: {
|
|
@@ -104,6 +106,15 @@ export default defineComponent({
|
|
|
104
106
|
playerHeight(): void {
|
|
105
107
|
this.$emit("hide", 0 === this.playerHeight);
|
|
106
108
|
},
|
|
109
|
+
display(): void {
|
|
110
|
+
if (this.display) {
|
|
111
|
+
this.displayWithTimeout = this.display;
|
|
112
|
+
} else {
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
this.displayWithTimeout = this.display;
|
|
115
|
+
}, 3000);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
107
118
|
},
|
|
108
119
|
|
|
109
120
|
methods: {
|
|
@@ -141,6 +152,12 @@ export default defineComponent({
|
|
|
141
152
|
transition: height 1s;
|
|
142
153
|
background: #282828 !important;
|
|
143
154
|
font-size: 1rem;
|
|
155
|
+
.medium-text {
|
|
156
|
+
font-size: 0.65rem;
|
|
157
|
+
}
|
|
158
|
+
.small-text {
|
|
159
|
+
font-size: 0.5rem;
|
|
160
|
+
}
|
|
144
161
|
|
|
145
162
|
@media (max-width: 960px) {
|
|
146
163
|
.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
|
|
|
@@ -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
|
@@ -13,7 +13,7 @@ const state: ParamStore = {
|
|
|
13
13
|
isProduction: false,
|
|
14
14
|
isContribution: false,
|
|
15
15
|
isRadio: false,
|
|
16
|
-
ApiUri: "https://api.
|
|
16
|
+
ApiUri: "https://api.dev2.saooti.org/",
|
|
17
17
|
podcastmaker: false,
|
|
18
18
|
buttonPlus: true,
|
|
19
19
|
allCategories: [],
|
|
@@ -27,8 +27,8 @@ const state: ParamStore = {
|
|
|
27
27
|
SharePlayer: true,
|
|
28
28
|
ShareButtons: true,
|
|
29
29
|
ShareDistribution: true,
|
|
30
|
-
MiniplayerUri: "https://playerbeta.
|
|
31
|
-
hlsUri: "https://hls.live.
|
|
30
|
+
MiniplayerUri: "https://playerbeta.dev2.saooti.org/",
|
|
31
|
+
hlsUri: "https://hls.live.dev2.saooti.org/",
|
|
32
32
|
mainRubrique: 0,
|
|
33
33
|
resourceUrl: undefined,
|
|
34
34
|
podcastItemShowEmission: false,
|
|
@@ -80,14 +80,14 @@ const state: ParamStore = {
|
|
|
80
80
|
userName: "",
|
|
81
81
|
},
|
|
82
82
|
octopusApi: {
|
|
83
|
-
url: "https://api.
|
|
84
|
-
commentsUrl: "https://comments.
|
|
85
|
-
imageUrl: "https://imageproxy.
|
|
86
|
-
studioUrl: "https://studio.
|
|
87
|
-
playerUrl: "https://playerbeta.
|
|
88
|
-
speechToTextUrl: "https://speech2text.
|
|
89
|
-
radioUrl:"https://radio.
|
|
90
|
-
recoUrl: "https://reco.
|
|
83
|
+
url: "https://api.dev2.saooti.org/",
|
|
84
|
+
commentsUrl: "https://comments.dev2.saooti.org/",
|
|
85
|
+
imageUrl: "https://imageproxy.dev2.saooti.org/",
|
|
86
|
+
studioUrl: "https://studio.dev2.saooti.org/",
|
|
87
|
+
playerUrl: "https://playerbeta.dev2.saooti.org/",
|
|
88
|
+
speechToTextUrl: "https://speech2text.dev2.saooti.org/",
|
|
89
|
+
radioUrl:"https://radio.dev2.saooti.org/",
|
|
90
|
+
recoUrl: "https://reco.dev2.saooti.org/",
|
|
91
91
|
organisationId: undefined,
|
|
92
92
|
rubriqueIdFilter: undefined,
|
|
93
93
|
},
|
|
@@ -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,7 @@ interface PlayerState {
|
|
|
22
24
|
playerTranscript?: Transcript;
|
|
23
25
|
playerLargeVersion: boolean;
|
|
24
26
|
playerVideo: boolean;
|
|
27
|
+
playerChaptering?: Chaptering;
|
|
25
28
|
}
|
|
26
29
|
export const usePlayerStore = defineStore("PlayerStore", {
|
|
27
30
|
state: (): PlayerState => ({
|
|
@@ -36,13 +39,33 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
36
39
|
playerSeekTime: 0,
|
|
37
40
|
playerLargeVersion: false,
|
|
38
41
|
playerVideo: false,
|
|
42
|
+
playerChaptering: undefined,
|
|
39
43
|
}),
|
|
40
44
|
getters: {
|
|
45
|
+
playerChapteringPercent(): ChapteringPercent|undefined{
|
|
46
|
+
if(!this.playerChaptering || 0===this.playerTotal){
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let chapteringPercent: ChapteringPercent = [];
|
|
50
|
+
for (let i = 0, len = this.playerChaptering.chapters.length; i < len; i++) {
|
|
51
|
+
chapteringPercent.push({
|
|
52
|
+
startTime : this.playerChaptering.chapters[i].startTime,
|
|
53
|
+
startDisplay: DurationHelper.formatDuration(this.playerChaptering.chapters[i].startTime, ':', false),
|
|
54
|
+
startPercent: (this.playerChaptering.chapters[i].startTime * 100 ) / (Math.round(this.playerTotal)),
|
|
55
|
+
endPercent:100,
|
|
56
|
+
title: this.playerChaptering.chapters[i].title
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0, len = chapteringPercent.length; i < len; i++) {
|
|
60
|
+
chapteringPercent[i].endPercent = chapteringPercent[i].startPercent + ((chapteringPercent[i+1]?.startPercent ?? 100) - chapteringPercent[i].startPercent);
|
|
61
|
+
}
|
|
62
|
+
return chapteringPercent;
|
|
63
|
+
},
|
|
41
64
|
playerHeight() {
|
|
42
65
|
if ("STOPPED" === this.playerStatus) return 0;
|
|
43
66
|
if (this.playerVideo) return "0px"/* "281px" */;
|
|
44
67
|
if (this.playerLargeVersion) return "27rem";
|
|
45
|
-
if (window.innerWidth > 450) return "
|
|
68
|
+
if (window.innerWidth > 450) return "6rem";
|
|
46
69
|
return "3.5rem";
|
|
47
70
|
},
|
|
48
71
|
playedTime(): string {
|
|
@@ -91,7 +114,7 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
91
114
|
},
|
|
92
115
|
},
|
|
93
116
|
actions: {
|
|
94
|
-
playerPlay(param?: any, isVideo = false) {
|
|
117
|
+
async playerPlay(param?: any, isVideo = false) {
|
|
95
118
|
if (!param) {
|
|
96
119
|
this.playerStatus = "STOPPED";
|
|
97
120
|
this.playerPodcast = undefined;
|
|
@@ -100,6 +123,7 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
100
123
|
this.playerRadio = undefined;
|
|
101
124
|
this.playerElapsed = 0;
|
|
102
125
|
this.playerVideo = false;
|
|
126
|
+
this.playerChaptering=undefined;
|
|
103
127
|
return;
|
|
104
128
|
}
|
|
105
129
|
if (
|
|
@@ -118,16 +142,26 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
118
142
|
this.playerRadio = undefined;
|
|
119
143
|
this.playerVideo = isVideo;
|
|
120
144
|
this.playerElapsed = 0;
|
|
145
|
+
this.playerChaptering=undefined;
|
|
121
146
|
if (
|
|
122
147
|
param.conferenceId &&
|
|
123
148
|
(!param.podcastId || param.processingStatus !== "READY")
|
|
124
149
|
) {
|
|
125
150
|
this.playerLive = param;
|
|
126
|
-
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (param.podcastId) {
|
|
127
154
|
this.playerPodcast = param;
|
|
128
|
-
|
|
155
|
+
if(param.annotations?.chaptering){
|
|
156
|
+
this.playerChaptering = await octopusApi.fetchDataPublic<Chaptering>(4, (param.annotations.chaptering as string));
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (param.mediaId) {
|
|
129
161
|
this.playerMedia = param;
|
|
130
|
-
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (param.canalId) {
|
|
131
165
|
this.playerRadio = { ...param, ...{ isInit: false } };
|
|
132
166
|
}
|
|
133
167
|
},
|
|
@@ -158,9 +192,11 @@ export const usePlayerStore = defineStore("PlayerStore", {
|
|
|
158
192
|
this.playerRadio.podcast = podcast;
|
|
159
193
|
},
|
|
160
194
|
|
|
161
|
-
playerUpdateElapsed(elapsed: number, total
|
|
195
|
+
playerUpdateElapsed(elapsed: number, total?: number) {
|
|
162
196
|
this.playerElapsed = elapsed;
|
|
163
|
-
|
|
197
|
+
if(total){
|
|
198
|
+
this.playerTotal = total;
|
|
199
|
+
}
|
|
164
200
|
},
|
|
165
201
|
|
|
166
202
|
playerUpdateTranscript(transcript?: Transcript) {
|
|
@@ -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>;
|