@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "38.0.32",
3
+ "version": "38.0.34",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -111,6 +111,7 @@ body{
111
111
  margin: 1rem 0;
112
112
  }
113
113
  }
114
+
114
115
  .flex-super-grow{
115
116
  flex-grow: 2;
116
117
  }
@@ -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("SShare in one click") }}
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
- this.targetElement.addEventListener("click", this.setPopoverData);
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
- this.targetElement.removeEventListener("click", this.setPopoverData);
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" href="/sso/logout" realLink="true">
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("homePriv" !== this.$route.name){
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, ["playerMedia"]),
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="display">
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
 
@@ -5,6 +5,7 @@
5
5
  :secondary-progress="percentLiveProgress"
6
6
  :alert-bar="displayAlertBar ? durationLivePosition : undefined"
7
7
  :class="classProgress"
8
+ :inPlayer="true"
8
9
  @mouseup="seekTo"
9
10
  />
10
11
  <CommentPlayer
@@ -41,7 +41,7 @@ export const playerLogicProgress = defineComponent({
41
41
  );
42
42
  },
43
43
  playerSeekTime() {
44
- if (!this.playerSeekTime) {
44
+ if (undefined===this.playerSeekTime) {
45
45
  return;
46
46
  }
47
47
  if (this.playerPodcast || this.playerLive) {
@@ -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
- (this.playerTranscript?.value[this.playerTranscript?.actual]?.endTime ??
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?.actual].text ??
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
- :href="authenticated ? '/sso/logout' : '/sso/login'"
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 && true === state.generalParameters.isRadio &&
94
+ (true === this.authenticated &&
95
+ true === state.generalParameters.isRadio &&
95
96
  this.myOrganisationId === this.radio?.organisationId) ||
96
97
  true === state.generalParameters.isAdmin
97
98
  );
@@ -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
@@ -350,4 +350,5 @@ export default {
350
350
  "High version":"Hohe Version",
351
351
  "Generate a social media post (with AI)":"Einen Social-Media-Beitrag erstellen (mit KI)",
352
352
  "Share in one click":"Mit einem Klick teilen",
353
+ "Chaptering":"Kapitelaufteilung",
353
354
  }
package/src/locale/en.ts CHANGED
@@ -350,4 +350,5 @@ export default {
350
350
  "High version":"High version",
351
351
  "Generate a social media post (with AI)":"Generate a social media post (with AI)",
352
352
  "Share in one click":"Share in one click",
353
+ "Chaptering":"Chaptering",
353
354
  };
package/src/locale/es.ts CHANGED
@@ -351,4 +351,5 @@ export default {
351
351
  "High version":"Versión alta",
352
352
  "Generate a social media post (with AI)":"Generar una publicación en las redes sociales (con IA)",
353
353
  "Share in one click":"Comparte en un clic",
354
+ "Chaptering":"Capítulos",
354
355
  }
package/src/locale/fr.ts CHANGED
@@ -357,4 +357,5 @@ export default {
357
357
  "High version":"Version en hauteur",
358
358
  "Generate a social media post (with AI)":"Générer un post réseaux sociaux (avec IA)",
359
359
  "Share in one click":"Partager en un clic",
360
+ "Chaptering":"Chapitrage",
360
361
  };
package/src/locale/it.ts CHANGED
@@ -343,4 +343,5 @@ export default{
343
343
  "High version":"Versione alta",
344
344
  "Generate a social media post (with AI)":"Genera un post sui social media (con AI)",
345
345
  "Share in one click":"Condividi con un clic",
346
+ "Chaptering":"Capitolazione",
346
347
  };
package/src/locale/sl.ts CHANGED
@@ -340,4 +340,5 @@ export default {
340
340
  "High version":"Visoka različica",
341
341
  "Generate a social media post (with AI)":"Ustvarite objavo v družabnem omrežju (z AI)",
342
342
  "Share in one click":"Delite z enim klikom",
343
+ "Chaptering":"Poglavje",
343
344
  }
@@ -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 "5rem";
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
- } else if (param.podcastId) {
154
+ return;
155
+ }
156
+ if (param.podcastId) {
127
157
  this.playerPodcast = param;
128
- } else if (param.mediaId) {
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
- } else if (param.canalId) {
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: number) {
198
+ playerUpdateElapsed(elapsed: number, total?: number) {
162
199
  this.playerElapsed = elapsed;
163
- this.playerTotal = total;
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>;