@saooti/octopus-sdk 41.1.8 → 41.1.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 41.1.10 (15/12/2025)
4
+
5
+ **Misc**
6
+
7
+ - Les pages d'émissions et d'épisodes peuvent afficher le titre de l'émission
8
+ en header
9
+ - Ajustement pour assurer l'affichage d'épisodes d'émissions différentes dans
10
+ `PodcastPresentationList`.
11
+
12
+ **Fixes**
13
+
14
+ - Correction d'un problème de lecture des lives sous Firefox
15
+
16
+ ## 41.1.9 (11/12/2025)
17
+
18
+ **Fixes**
19
+
20
+ - Correction recherche dans `PodcastList`
21
+
3
22
  ## 41.1.8 (10/12/2025)
4
23
 
5
24
  **Fixes**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.1.8",
3
+ "version": "41.1.10",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -70,7 +70,7 @@ export interface PodcastSearchOptions extends Paginable<PodcastSort> {
70
70
  /** Filter by podcast having a video */
71
71
  withVideo?: boolean;
72
72
  /** Filter by tags */
73
- tags: Array<string>;
73
+ tags?: Array<string>;
74
74
  /** Filter by beneficiaries/rights holder reference */
75
75
  beneficiaries?: Array<string>;
76
76
  }
@@ -1,7 +1,7 @@
1
1
  import stringHelper from "../../../helper/stringHelper";
2
2
  import { usePlayerLogicProgress } from "./usePlayerLogicProgress";
3
3
  import { computed, Ref, ref } from "vue";
4
- import { usePlayerStore } from "../../../stores/PlayerStore";
4
+ import { usePlayerStore, PlayerStatus } from "../../../stores/PlayerStore";
5
5
  import { useApiStore } from "../../../stores/ApiStore";
6
6
  import dayjs from "dayjs";
7
7
  import { useAuthStore } from "../../../stores/AuthStore";
@@ -28,11 +28,8 @@ export const usePlayerLive = (hlsReady: Ref<boolean>)=>{
28
28
  return authStore.authParam.accessToken && ("SECURED" === playerStore.playerLive?.organisation?.privacy || playerStore.playerRadio?.secured);
29
29
  });
30
30
 
31
-
32
-
33
-
34
- function onPlay(): void {
35
- playerStore.playerChangeStatus("PAUSED"===playerStore.playerStatus);
31
+ function onPlay(): void {
32
+ playerStore.playerChangeStatus(PlayerStatus.PAUSED ===playerStore.playerStatus);
36
33
  }
37
34
 
38
35
  function playRadio() {
@@ -87,7 +84,8 @@ export const usePlayerLive = (hlsReady: Ref<boolean>)=>{
87
84
  } else {
88
85
  await initHls();
89
86
  }
90
- } catch {
87
+ } catch(e) {
88
+ console.error(e);
91
89
  onHlsError();
92
90
  }
93
91
  }
@@ -112,6 +110,11 @@ export const usePlayerLive = (hlsReady: Ref<boolean>)=>{
112
110
  throw new Error("Hls is not supported ! ");
113
111
  }
114
112
  hls.value = new Hls({
113
+ autoStartLoad: true,
114
+ liveDurationInfinity:true,
115
+ backBufferLength:10,
116
+ maxBufferLength:60,
117
+
115
118
  xhrSetup: (xhr: XMLHttpRequest) => {
116
119
  if (needToAddToken.value) {
117
120
  xhr.setRequestHeader("Authorization", "Bearer " +authStore.authParam.accessToken);
@@ -124,16 +127,36 @@ export const usePlayerLive = (hlsReady: Ref<boolean>)=>{
124
127
  if(true===errorHls.value){
125
128
  return;
126
129
  }
127
- playPromise.value = (audioElement.value as HTMLAudioElement).play();
128
- playPromise.value.then(() =>{
129
- playPromise.value = undefined;
130
- onPlay();
131
- }).catch(()=>{
132
- onHlsError();
133
- playPromise.value = undefined;
134
- })
130
+ setTimeout(() => {
131
+ playPromise.value = (audioElement.value as HTMLAudioElement).play();
132
+ playPromise.value.then(() =>{
133
+ playPromise.value = undefined;
134
+ onPlay();
135
+ }).catch((e)=>{
136
+ console.error(e);
137
+ onHlsError();
138
+ playPromise.value = undefined;
139
+ });
140
+ }, 500);
135
141
  });
136
- hls.value.on(Hls.Events.ERROR, async (e, data:any) => {
142
+ hls.value.on(Hls.Events.ERROR, async (name: string, data:any) => {
143
+ // Stalling, we don't have enough data
144
+ if (data.details === 'bufferStalledError') {
145
+ // Some logs to be able to follow what's happening
146
+ console.warn('playback is stalling…');
147
+ playerStore.playerStatus = PlayerStatus.LOADING;
148
+ // Destroy current instance
149
+ hls.value.destroy();
150
+ // Wait a bit before restarting playback
151
+ setTimeout(initHls, 500);
152
+ return;
153
+ }
154
+ console.error('An error occured: ' + name + ' / ' + data.details);
155
+ console.error(data);
156
+ if (data.fatal && data.type === Hls.ErrorTypes.MEDIA_ERROR) {
157
+ console.warn('Trying to recover...');
158
+ hls.value.recoverMediaError();
159
+ }
137
160
  errorHls.value = true;
138
161
  if(undefined===playPromise.value && data.fatal){
139
162
  onHlsError();
@@ -186,8 +186,12 @@ export const usePlayerLogic = (forceHide: Ref<boolean, boolean>) => {
186
186
  let streamDuration = mediaTarget.duration;
187
187
  if (Infinity === streamDuration) {
188
188
  const seekable = mediaTarget.seekable;
189
- if (seekable) {
190
- streamDuration = seekable.end(seekable.length - 1);
189
+ if (seekable && seekable.length > 0) {
190
+ try {
191
+ streamDuration = seekable.end(seekable.length - 1);
192
+ } catch (e) {
193
+ console.error(e);
194
+ }
191
195
  } else {
192
196
  streamDuration = mediaTarget.currentTime;
193
197
  }
@@ -272,4 +276,4 @@ export const usePlayerLogic = (forceHide: Ref<boolean, boolean>) => {
272
276
  onFinished,
273
277
  onPlay
274
278
  }
275
- }
279
+ }
@@ -86,7 +86,7 @@ const props = withDefaults(defineProps<{
86
86
  showCount?: boolean;
87
87
  displaySortText?: boolean;
88
88
  sortCriteria?: PodcastSort;
89
- validity?: 'true'|'false'|boolean;
89
+ validity?: 'true'|'false'|''|boolean; // TODO improve this
90
90
  rubriqueId?: Array<number>;
91
91
  rubriquageId?: Array<number>;
92
92
  noRubriquageId?: Array<number>;
@@ -188,8 +188,10 @@ onBeforeMount(()=>fetchContent(false))
188
188
  async function fetchContent(reset: boolean): Promise<void> {
189
189
  loading.value = true;
190
190
 
191
+ // Sadly we kinda have no idea what will be passed as 'validity', we need to
192
+ // handle multiple cases.
191
193
  let validity: undefined|boolean = undefined;
192
- if (props.validity !== undefined) {
194
+ if (props.validity !== undefined && props.validity !== '') {
193
195
  validity = props.validity === 'true' || props.validity === true;
194
196
  }
195
197
 
@@ -43,15 +43,15 @@ import { Emission } from "@/stores/class/general/emission";
43
43
  import { onMounted, Ref, ref } from "vue";
44
44
  import { AxiosError } from "axios";
45
45
  import {useResizePhone} from "../../composable/useResizePhone";
46
- import { ListClassicReturn } from "@/stores/class/general/listReturn";
46
+ import { ListClassicReturn } from "../../../stores/class/general/listReturn";
47
47
 
48
48
  import PresentationLayout from "../../layout/PresentationLayout.vue";
49
- import { Podcast } from "@/stores/class/general/podcast";
50
- import { ModuleApi } from "../../../api/apiConnection";
49
+ import { Podcast, SimplifiedPodcast, simplifiedToFull } from "../../../stores/class/general/podcast";
51
50
 
52
51
  import PresentationItem from "../../layout/PresentationItem.vue";
53
52
  import PodcastPlayButton from "./PodcastPlayButton.vue";
54
53
  import { RouteLocationRaw } from "vue-router";
54
+ import { podcastApi, PodcastSort } from "../../../api/podcastApi";
55
55
 
56
56
  //Props
57
57
  const props = defineProps({
@@ -92,23 +92,29 @@ async function fetchNext(): Promise<void> {
92
92
  specialTreatement: true,
93
93
  });
94
94
 
95
- // Retrieve the podcasts for these emissions
96
- const data = await classicApi.fetchData<ListClassicReturn<Podcast>>({
97
- api: ModuleApi.DEFAULT,
98
- path: "podcast/search",
99
- parameters: {
95
+ const promises: Array<Promise<SimplifiedPodcast>> = [];
96
+
97
+ for (let i = 0; i < emissions.result.length; i++) {
98
+ promises.push(podcastApi.search({
100
99
  first: 0,
101
- size: 5,
102
- organisationId: props.organisationId,
103
- emissionId: emissions.result.map(e => e.emissionId),
104
- sort: "DATE",
100
+ pageSize: 1,
101
+ organisationId: [props.organisationId],
102
+ emissionId: [emissions.result[i].emissionId],
103
+ sort: PodcastSort.DATE,
105
104
  rubriqueId: props.rubriquesId
106
- },
107
- specialTreatement: true
108
- });
109
-
105
+ }).then(r => r.result[0]));
106
+ }
107
+
108
+ // Retrieve the podcasts for these emissions
109
+ const data = await Promise.all(promises);
110
+
110
111
  podcasts.value = podcasts.value.concat(
111
- data.result.filter((em: Podcast | null) => null !== em),
112
+ data.filter((em: SimplifiedPodcast | null) => null !== em).map(p => {
113
+ // Get emission from podcast
114
+ const emission = emissions.result.find(e => e.emissionId === p.emissionId);
115
+ // Create full podcast from simplified + emission
116
+ return simplifiedToFull(p, emission.orga, emission);
117
+ })
112
118
  );
113
119
  loading.value = false;
114
120
  } catch (errorWs) {
@@ -3,7 +3,7 @@
3
3
  <template v-if="loaded && !error && emission">
4
4
  <PodcastmakerHeader
5
5
  v-if="isPodcastmaker"
6
- :page-title="t('Emission')"
6
+ :page-title="useEmissionTitle ? emission.name : t('Emission')"
7
7
  :img-url="emission.imageUrl"
8
8
  />
9
9
  <div
@@ -178,7 +178,8 @@ const props = defineProps({
178
178
  pr: { default: 0, type: Number },
179
179
  ps: { default: 30, type: Number },
180
180
  routeQuery: { default: "", type: String },
181
- })
181
+ useEmissionTitle: { default: false, type: Boolean }
182
+ });
182
183
 
183
184
 
184
185
  //Data
@@ -3,7 +3,7 @@
3
3
  <template v-if="loaded && !error && podcast">
4
4
  <PodcastmakerHeader
5
5
  v-if="isPodcastmaker"
6
- :page-title="titlePage"
6
+ :page-title="useEmissionTitle ? podcast.emission.name : titlePage"
7
7
  :img-url="podcast.imageUrl"
8
8
  />
9
9
  <div
@@ -114,6 +114,8 @@ const props = defineProps({
114
114
  updateStatus: { default: undefined, type: String },
115
115
  playingPodcast: { default: undefined, type: Object as () => Podcast },
116
116
  podcastId: { default: 0, type: Number },
117
+ /** When true, display emission title in podcastmaker header */
118
+ useEmissionTitle: { default: false, type: Boolean }
117
119
  });
118
120
 
119
121
 
@@ -14,9 +14,17 @@ interface Transcript {
14
14
  actualText: string;
15
15
  value: Array<{ endTime: number; startTime: number; text: string }>;
16
16
  }
17
+
18
+ export enum PlayerStatus {
19
+ STOPPED = "STOPPED",
20
+ LOADING = "LOADING",
21
+ PLAYING = "PLAYING",
22
+ PAUSED = "PAUSED"
23
+ }
24
+
17
25
  interface PlayerState {
18
26
  playerCurrentChange: number | null;
19
- playerStatus: string; //STOPPED, LOADING, PLAYING, PAUSED
27
+ playerStatus: PlayerStatus;
20
28
  playerPodcast: Podcast | undefined;
21
29
  playerVolume?: number; //From 0 to 1
22
30
  playerElapsed: number; //From 0 to 1