@streamscloud/kit 0.0.1-1771006476137 → 0.0.1-1771075495185
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/dist/ui/player/buttons/cmp.mobile-player-buttons.svelte +37 -0
- package/dist/ui/player/buttons/cmp.mobile-player-buttons.svelte.d.ts +7 -0
- package/dist/ui/player/buttons/cmp.player-buttons.svelte +127 -0
- package/dist/ui/player/buttons/cmp.player-buttons.svelte.d.ts +9 -0
- package/dist/ui/player/buttons/cmp.responsive-player-buttons.svelte +32 -0
- package/dist/ui/player/buttons/cmp.responsive-player-buttons.svelte.d.ts +8 -0
- package/dist/ui/player/buttons/index.d.ts +4 -0
- package/dist/ui/player/buttons/index.js +3 -0
- package/dist/ui/player/buttons/types.d.ts +7 -0
- package/dist/ui/player/buttons/types.js +1 -0
- package/dist/ui/player/carousel/carousel-localization.d.ts +3 -0
- package/dist/ui/player/carousel/carousel-localization.js +12 -0
- package/dist/ui/player/carousel/cmp.carousel.svelte +418 -0
- package/dist/ui/player/carousel/cmp.carousel.svelte.d.ts +38 -0
- package/dist/ui/player/carousel/index.d.ts +2 -0
- package/dist/ui/player/carousel/index.js +1 -0
- package/dist/ui/player/carousel/types.d.ts +1 -0
- package/dist/ui/player/carousel/types.js +1 -0
- package/dist/ui/player/feed-slider/cmp.feed-slider.svelte +206 -0
- package/dist/ui/player/feed-slider/cmp.feed-slider.svelte.d.ts +43 -0
- package/dist/ui/player/feed-slider/index.d.ts +3 -0
- package/dist/ui/player/feed-slider/index.js +2 -0
- package/dist/ui/player/feed-slider/prevent-feed-scroll.d.ts +5 -0
- package/dist/ui/player/feed-slider/prevent-feed-scroll.js +11 -0
- package/dist/ui/player/feed-slider/types.d.ts +16 -0
- package/dist/ui/player/feed-slider/types.js +1 -0
- package/dist/ui/player/feed-slider/wheel-gestures-adapter.d.ts +17 -0
- package/dist/ui/player/feed-slider/wheel-gestures-adapter.js +79 -0
- package/dist/ui/player/providers/chunks-player-buffer/index.d.ts +2 -0
- package/dist/ui/player/providers/chunks-player-buffer/index.js +2 -0
- package/dist/ui/player/providers/chunks-player-buffer/player-chunk-item.svelte.d.ts +9 -0
- package/dist/ui/player/providers/chunks-player-buffer/player-chunk-item.svelte.js +9 -0
- package/dist/ui/player/providers/chunks-player-buffer/player-chunk.svelte.d.ts +30 -0
- package/dist/ui/player/providers/chunks-player-buffer/player-chunk.svelte.js +72 -0
- package/dist/ui/player/providers/chunks-player-buffer/player-chunks-manager.svelte.d.ts +23 -0
- package/dist/ui/player/providers/chunks-player-buffer/player-chunks-manager.svelte.js +220 -0
- package/dist/ui/player/providers/default-chunks-player-buffer.svelte.d.ts +23 -0
- package/dist/ui/player/providers/default-chunks-player-buffer.svelte.js +71 -0
- package/dist/ui/player/providers/default-feed-player-buffer.svelte.d.ts +29 -0
- package/dist/ui/player/providers/default-feed-player-buffer.svelte.js +121 -0
- package/dist/ui/player/providers/index.d.ts +4 -0
- package/dist/ui/player/providers/index.js +3 -0
- package/dist/ui/player/providers/service.d.ts +2 -0
- package/dist/ui/player/providers/service.js +13 -0
- package/dist/ui/player/providers/types.d.ts +54 -0
- package/dist/ui/player/providers/types.js +1 -0
- package/dist/ui/player/utils/index.d.ts +1 -0
- package/dist/ui/player/utils/index.js +1 -0
- package/dist/ui/player/utils/touch-synchronizer.d.ts +7 -0
- package/dist/ui/player/utils/touch-synchronizer.js +21 -0
- package/package.json +23 -5
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Deferred } from '../../../../core';
|
|
2
|
+
import { PlayerChunk } from './player-chunk.svelte';
|
|
3
|
+
const ITEMS_BUFFER_SIZE = 10;
|
|
4
|
+
// Configuration: if true, always start from first item when switching chunks
|
|
5
|
+
// if false, activate the exact item at its position in the new chunk
|
|
6
|
+
const START_FROM_FIRST_ITEM_ON_CHUNK_SWITCH = true;
|
|
7
|
+
export class PlayerChunksManager {
|
|
8
|
+
provider;
|
|
9
|
+
_activeChunkIndex = $state(-1);
|
|
10
|
+
_loadedChunks = $state.raw([]);
|
|
11
|
+
_warmUpDeferred = $state.raw(null);
|
|
12
|
+
constructor(provider) {
|
|
13
|
+
this.provider = provider;
|
|
14
|
+
}
|
|
15
|
+
get activeChunk() {
|
|
16
|
+
return this._loadedChunks[this._activeChunkIndex] ?? null;
|
|
17
|
+
}
|
|
18
|
+
get loadedChunks() {
|
|
19
|
+
return this._loadedChunks;
|
|
20
|
+
}
|
|
21
|
+
get isLoading() {
|
|
22
|
+
return this._warmUpDeferred !== null || this._loadedChunks.some((c) => c.isLoading);
|
|
23
|
+
}
|
|
24
|
+
get flattenedChunkItems() {
|
|
25
|
+
return this._loadedChunks.reduce((acc, chunk) => {
|
|
26
|
+
acc.push(...chunk.items);
|
|
27
|
+
return acc;
|
|
28
|
+
}, []);
|
|
29
|
+
}
|
|
30
|
+
get flattenedActiveChunkItemIndex() {
|
|
31
|
+
if (!this.activeChunk || !this.activeChunk.activeChunkItem) {
|
|
32
|
+
return -1;
|
|
33
|
+
}
|
|
34
|
+
let itemsBeforeActiveChunk = 0;
|
|
35
|
+
for (let i = 0; i < this._activeChunkIndex; i++) {
|
|
36
|
+
itemsBeforeActiveChunk += this._loadedChunks[i].items.length;
|
|
37
|
+
}
|
|
38
|
+
return itemsBeforeActiveChunk + this.activeChunk.activeItemIndex;
|
|
39
|
+
}
|
|
40
|
+
tryActivateItemById = (id) => {
|
|
41
|
+
const chunkWithItemIndex = this._loadedChunks.findIndex((chunk) => chunk.items.some((item) => item.id === id));
|
|
42
|
+
const chunkWithItem = this._loadedChunks[chunkWithItemIndex];
|
|
43
|
+
if (!chunkWithItem) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const itemIndex = chunkWithItem.items.findIndex((item) => item.id === id);
|
|
47
|
+
this.setActiveChunkIndex(chunkWithItemIndex, itemIndex);
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
removeItemById = (id) => {
|
|
51
|
+
const itemIndex = this.flattenedChunkItems.findIndex((item) => item.id === id);
|
|
52
|
+
if (itemIndex === -1) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const currentItem = this.flattenedChunkItems[this.flattenedActiveChunkItemIndex];
|
|
56
|
+
// Find next item to activate BEFORE removing (if removing current item)
|
|
57
|
+
let nextItemId = currentItem?.id;
|
|
58
|
+
if (this.flattenedActiveChunkItemIndex === itemIndex) {
|
|
59
|
+
// Try next item
|
|
60
|
+
if (itemIndex + 1 < this.flattenedChunkItems.length) {
|
|
61
|
+
nextItemId = this.flattenedChunkItems[itemIndex + 1].id;
|
|
62
|
+
}
|
|
63
|
+
// Try previous item
|
|
64
|
+
else if (itemIndex > 0) {
|
|
65
|
+
nextItemId = this.flattenedChunkItems[itemIndex - 1].id;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// No items left
|
|
69
|
+
nextItemId = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Remove item from chunks
|
|
73
|
+
this._loadedChunks.forEach((chunk) => {
|
|
74
|
+
if (chunk.chunkItems.some((i) => i.model.id === id)) {
|
|
75
|
+
chunk.mutateChunkItems(chunk.chunkItems.filter((i) => i.model.id !== id));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (nextItemId) {
|
|
79
|
+
this.tryActivateItemById(nextItemId);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this._activeChunkIndex = -1;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
removeChunkById = (id) => {
|
|
87
|
+
const chunkIndex = this._loadedChunks.findIndex((chunk) => chunk.model.id === id);
|
|
88
|
+
if (chunkIndex === -1) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const isActiveChunk = this._activeChunkIndex === chunkIndex;
|
|
92
|
+
let newActiveChunkIndex = this._activeChunkIndex;
|
|
93
|
+
this._loadedChunks = [...this._loadedChunks.slice(0, chunkIndex), ...this._loadedChunks.slice(chunkIndex + 1)];
|
|
94
|
+
if (this._loadedChunks.length === 0) {
|
|
95
|
+
this._activeChunkIndex = -1;
|
|
96
|
+
this.provider.onEndReached?.();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (isActiveChunk) {
|
|
100
|
+
if (chunkIndex < this._loadedChunks.length) {
|
|
101
|
+
newActiveChunkIndex = chunkIndex;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
newActiveChunkIndex = this._loadedChunks.length - 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (this._activeChunkIndex > chunkIndex) {
|
|
108
|
+
newActiveChunkIndex--;
|
|
109
|
+
}
|
|
110
|
+
this.setActiveChunkIndex(newActiveChunkIndex, 0);
|
|
111
|
+
return true;
|
|
112
|
+
};
|
|
113
|
+
initialize = async () => {
|
|
114
|
+
this._loadedChunks = this.provider.initialData.prefetchedChunks.map((chunk) => new PlayerChunk({ chunk, provider: { loadChunkItems: this.provider.loadChunkItems } }));
|
|
115
|
+
await this.warmUp();
|
|
116
|
+
if (this._loadedChunks.length === 0 || this.flattenedChunkItems.length === 0) {
|
|
117
|
+
this.provider.onEndReached?.();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const firstFilledChunkIndex = this._loadedChunks.findIndex((c) => c.items.length > 0);
|
|
121
|
+
const initialStartItemIndex = firstFilledChunkIndex === 0 && this.provider.initialData.startItemIndex && this.provider.initialData.startItemIndex > 0
|
|
122
|
+
? this.provider.initialData.startItemIndex
|
|
123
|
+
: 0;
|
|
124
|
+
this.setActiveChunkIndex(firstFilledChunkIndex, initialStartItemIndex);
|
|
125
|
+
};
|
|
126
|
+
setActiveChunkIndex = async (index, chunkItemIndex) => {
|
|
127
|
+
this._activeChunkIndex = index;
|
|
128
|
+
this.activeChunk.setActiveItemIndex(chunkItemIndex);
|
|
129
|
+
// Don't wait for warm up to be finished, it runs in the background
|
|
130
|
+
this.warmUp();
|
|
131
|
+
};
|
|
132
|
+
activateItemAtFlattenedIndex = async (index) => {
|
|
133
|
+
const activeChunkId = this.activeChunk?.model.id;
|
|
134
|
+
const flattenedItems = this.loadedChunks.flatMap((x) => x.chunkItems);
|
|
135
|
+
const nextItem = flattenedItems[index];
|
|
136
|
+
if (!nextItem) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (nextItem.chunkId !== activeChunkId) {
|
|
140
|
+
const nextChunkIndex = this.loadedChunks.findIndex((c) => c.model.id === nextItem.chunkId);
|
|
141
|
+
if (nextChunkIndex === -1) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const itemIndexInChunk = START_FROM_FIRST_ITEM_ON_CHUNK_SWITCH ? 0 : this.loadedChunks[nextChunkIndex].chunkItems.indexOf(nextItem);
|
|
145
|
+
this.setActiveChunkIndex(nextChunkIndex, itemIndexInChunk);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.activeChunk.setActiveItemIndex(this.activeChunk.chunkItems.indexOf(nextItem));
|
|
149
|
+
// Don't wait for warm up to be finished, it runs in the background
|
|
150
|
+
this.warmUp();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
warmUp = async () => {
|
|
154
|
+
if (this._warmUpDeferred) {
|
|
155
|
+
return this._warmUpDeferred.promise;
|
|
156
|
+
}
|
|
157
|
+
this._warmUpDeferred = new Deferred();
|
|
158
|
+
try {
|
|
159
|
+
await this.warmUpSequentially();
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
this._warmUpDeferred.resolve();
|
|
163
|
+
this._warmUpDeferred = null;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
reset = async () => {
|
|
167
|
+
this._activeChunkIndex = -1;
|
|
168
|
+
this._loadedChunks = [];
|
|
169
|
+
this._warmUpDeferred = null;
|
|
170
|
+
await this.warmUp();
|
|
171
|
+
};
|
|
172
|
+
warmUpSequentially = async () => {
|
|
173
|
+
const startChunkIndex = Math.max(0, this._activeChunkIndex);
|
|
174
|
+
// Calculate how many items we need ahead of current position
|
|
175
|
+
const getItemsAhead = () => {
|
|
176
|
+
const currentFlatIndex = this.flattenedActiveChunkItemIndex;
|
|
177
|
+
const totalItems = this.flattenedChunkItems.length;
|
|
178
|
+
return totalItems - currentFlatIndex - 1; // -1 because current item doesn't count
|
|
179
|
+
};
|
|
180
|
+
while (getItemsAhead() < ITEMS_BUFFER_SIZE) {
|
|
181
|
+
// Find first non-fully-loaded chunk starting from active
|
|
182
|
+
let targetChunkIndex = -1;
|
|
183
|
+
for (let i = startChunkIndex; i < this._loadedChunks.length; i++) {
|
|
184
|
+
if (this._loadedChunks[i].canLoadMore) {
|
|
185
|
+
targetChunkIndex = i;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// If all chunks are fully loaded, load more chunks
|
|
190
|
+
if (targetChunkIndex === -1) {
|
|
191
|
+
const result = await this.provider.loadMoreChunks();
|
|
192
|
+
if (result.length === 0) {
|
|
193
|
+
// No more chunks available
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
this._loadedChunks = [
|
|
197
|
+
...this._loadedChunks,
|
|
198
|
+
...result.map((chunk) => new PlayerChunk({
|
|
199
|
+
chunk,
|
|
200
|
+
provider: { loadChunkItems: this.provider.loadChunkItems }
|
|
201
|
+
}))
|
|
202
|
+
];
|
|
203
|
+
continue; // Re-check for chunks to fill
|
|
204
|
+
}
|
|
205
|
+
// Load one page from target chunk
|
|
206
|
+
const chunk = this._loadedChunks[targetChunkIndex];
|
|
207
|
+
const itemsBefore = chunk.items.length;
|
|
208
|
+
await chunk.loadMore();
|
|
209
|
+
const itemsAfter = chunk.items.length;
|
|
210
|
+
// If chunk became fully loaded but added no items, continue to next chunk
|
|
211
|
+
if (itemsAfter === itemsBefore && !chunk.canLoadMore) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// If no progress made and chunk is not fully loaded, something went wrong
|
|
215
|
+
if (itemsAfter === itemsBefore && chunk.canLoadMore) {
|
|
216
|
+
break; // Avoid infinite loop
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IChunksPlayerBuffer, IChunksPlayerDataProvider, WithId } from './types';
|
|
2
|
+
export declare class DefaultChunksPlayerBuffer<TItem extends WithId, TChunk extends WithId> implements IChunksPlayerBuffer<TItem> {
|
|
3
|
+
readonly kind = "chunks";
|
|
4
|
+
readonly loaded: TItem[];
|
|
5
|
+
readonly currentIndex: number;
|
|
6
|
+
readonly current: TItem | null;
|
|
7
|
+
readonly canLoadNext: boolean;
|
|
8
|
+
readonly canLoadPrevious: boolean;
|
|
9
|
+
readonly navigationDisabled: boolean;
|
|
10
|
+
readonly animationDuration = 500;
|
|
11
|
+
private _playerChunksManager;
|
|
12
|
+
private _onEndReachedFn;
|
|
13
|
+
constructor(provider: IChunksPlayerDataProvider<TItem, TChunk>);
|
|
14
|
+
get activeChunk(): import("./chunks-player-buffer").PlayerChunk<TItem, TChunk>;
|
|
15
|
+
setActiveChunkItemIndex: (index: number) => void;
|
|
16
|
+
tryActivateItemById: (id: string) => boolean;
|
|
17
|
+
removeChunkById: (id: string) => boolean | undefined;
|
|
18
|
+
removeItemById: (id: string) => boolean;
|
|
19
|
+
loadNext: () => Promise<void>;
|
|
20
|
+
loadPrevious: () => Promise<void>;
|
|
21
|
+
reset: () => void;
|
|
22
|
+
ensureWarmedUp: () => Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Utils } from '../../../core/utils';
|
|
2
|
+
import { PlayerChunksManager } from './chunks-player-buffer';
|
|
3
|
+
export class DefaultChunksPlayerBuffer {
|
|
4
|
+
kind = 'chunks';
|
|
5
|
+
loaded = $derived.by(() => this._playerChunksManager.flattenedChunkItems);
|
|
6
|
+
currentIndex = $derived.by(() => this._playerChunksManager.flattenedActiveChunkItemIndex);
|
|
7
|
+
current = $derived(this.currentIndex >= 0 ? this.loaded[this.currentIndex] : null);
|
|
8
|
+
canLoadNext = $derived.by(() => {
|
|
9
|
+
if (this.loaded.length && this.currentIndex < this.loaded.length - 1) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
// Check if manager is still loading
|
|
13
|
+
if (this._playerChunksManager.isLoading) {
|
|
14
|
+
return false; // Don't trigger onEndReached while loading
|
|
15
|
+
}
|
|
16
|
+
return !!this._onEndReachedFn;
|
|
17
|
+
});
|
|
18
|
+
canLoadPrevious = $derived(this.currentIndex > 0);
|
|
19
|
+
navigationDisabled = $derived(!this.canLoadNext && !this.canLoadPrevious);
|
|
20
|
+
animationDuration = 500;
|
|
21
|
+
_playerChunksManager;
|
|
22
|
+
_onEndReachedFn;
|
|
23
|
+
constructor(provider) {
|
|
24
|
+
this._onEndReachedFn = provider.onEndReached;
|
|
25
|
+
// Throttle navigation methods
|
|
26
|
+
this.loadNext = Utils.throttle(this.loadNext, this.animationDuration);
|
|
27
|
+
this.loadPrevious = Utils.throttle(this.loadPrevious, this.animationDuration);
|
|
28
|
+
this._playerChunksManager = new PlayerChunksManager(provider);
|
|
29
|
+
this._playerChunksManager.initialize();
|
|
30
|
+
}
|
|
31
|
+
get activeChunk() {
|
|
32
|
+
return this._playerChunksManager.activeChunk;
|
|
33
|
+
}
|
|
34
|
+
setActiveChunkItemIndex = (index) => {
|
|
35
|
+
if (!this.activeChunk) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.activeChunk.setActiveItemIndex(index);
|
|
39
|
+
};
|
|
40
|
+
tryActivateItemById = (id) => {
|
|
41
|
+
return this._playerChunksManager.tryActivateItemById(id);
|
|
42
|
+
};
|
|
43
|
+
removeChunkById = (id) => {
|
|
44
|
+
return this._playerChunksManager.removeChunkById(id);
|
|
45
|
+
};
|
|
46
|
+
removeItemById = (id) => {
|
|
47
|
+
return this._playerChunksManager.removeItemById(id);
|
|
48
|
+
};
|
|
49
|
+
loadNext = async () => {
|
|
50
|
+
if (!this.canLoadNext) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (this.currentIndex < this.loaded.length - 1) {
|
|
54
|
+
this._playerChunksManager.activateItemAtFlattenedIndex(this.currentIndex + 1);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this._onEndReachedFn?.();
|
|
58
|
+
};
|
|
59
|
+
loadPrevious = async () => {
|
|
60
|
+
if (!this.canLoadPrevious) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this._playerChunksManager.activateItemAtFlattenedIndex(this.currentIndex - 1);
|
|
64
|
+
};
|
|
65
|
+
reset = () => {
|
|
66
|
+
this._playerChunksManager.reset();
|
|
67
|
+
};
|
|
68
|
+
ensureWarmedUp = async () => {
|
|
69
|
+
await this._playerChunksManager.warmUp();
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IFeedPlayerBuffer, IFeedPlayerDataProvider, WithId } from './types';
|
|
2
|
+
export declare class DefaultFeedPlayerBuffer<T extends WithId> implements IFeedPlayerBuffer<T> {
|
|
3
|
+
readonly kind = "feed";
|
|
4
|
+
readonly loaded: (T & {
|
|
5
|
+
mediaIndex?: number;
|
|
6
|
+
})[];
|
|
7
|
+
readonly currentIndex: number;
|
|
8
|
+
readonly current: (T & {
|
|
9
|
+
mediaIndex?: number;
|
|
10
|
+
}) | null;
|
|
11
|
+
readonly canLoadNext: boolean;
|
|
12
|
+
readonly canLoadPrevious: boolean;
|
|
13
|
+
readonly navigationDisabled: boolean;
|
|
14
|
+
readonly animationDuration = 500;
|
|
15
|
+
private _currentIndex;
|
|
16
|
+
private _loaded;
|
|
17
|
+
private _loadMoreFn;
|
|
18
|
+
private _fetchDeferred;
|
|
19
|
+
private _onEndReachedFn;
|
|
20
|
+
constructor(provider: IFeedPlayerDataProvider<T>);
|
|
21
|
+
tryActivateItemById: (id: string) => boolean;
|
|
22
|
+
removeItemById: (id: string) => boolean;
|
|
23
|
+
loadNext: () => Promise<void>;
|
|
24
|
+
loadPrevious: () => Promise<void>;
|
|
25
|
+
reset: () => void;
|
|
26
|
+
ensureWarmedUp: () => Promise<void>;
|
|
27
|
+
private initializeBuffer;
|
|
28
|
+
private warmUpBuffer;
|
|
29
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Deferred } from '../../../core';
|
|
2
|
+
import { Utils } from '../../../core/utils';
|
|
3
|
+
const BUFFER_SIZE = 5;
|
|
4
|
+
export class DefaultFeedPlayerBuffer {
|
|
5
|
+
kind = 'feed';
|
|
6
|
+
loaded = $derived.by(() => this._loaded);
|
|
7
|
+
currentIndex = $derived.by(() => this._currentIndex);
|
|
8
|
+
current = $derived(this.currentIndex >= 0 ? this.loaded[this.currentIndex] : null);
|
|
9
|
+
canLoadNext = $derived.by(() => {
|
|
10
|
+
if (this.loaded.length && this.currentIndex < this.loaded.length - 1) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return !this._fetchDeferred && !!this._onEndReachedFn;
|
|
14
|
+
});
|
|
15
|
+
canLoadPrevious = $derived(this.currentIndex > 0);
|
|
16
|
+
navigationDisabled = $derived(!this.canLoadNext && !this.canLoadPrevious);
|
|
17
|
+
animationDuration = 500;
|
|
18
|
+
_currentIndex = $state(-1);
|
|
19
|
+
_loaded = $state.raw([]);
|
|
20
|
+
_loadMoreFn;
|
|
21
|
+
_fetchDeferred = $state.raw(null);
|
|
22
|
+
_onEndReachedFn;
|
|
23
|
+
constructor(provider) {
|
|
24
|
+
this._onEndReachedFn = provider.onEndReached;
|
|
25
|
+
this._loadMoreFn = provider.loadMore;
|
|
26
|
+
// Throttle navigation methods
|
|
27
|
+
this.loadNext = Utils.throttle(this.loadNext, this.animationDuration);
|
|
28
|
+
this.loadPrevious = Utils.throttle(this.loadPrevious, this.animationDuration);
|
|
29
|
+
this.initializeBuffer({ initialData: provider.initialData });
|
|
30
|
+
}
|
|
31
|
+
tryActivateItemById = (id) => {
|
|
32
|
+
const itemIndex = this._loaded.findIndex((item) => item.id === id);
|
|
33
|
+
if (itemIndex === -1) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
this._currentIndex = itemIndex;
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
removeItemById = (id) => {
|
|
40
|
+
const itemIndex = this._loaded.findIndex((item) => item.id === id);
|
|
41
|
+
if (itemIndex === -1) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
this._loaded = this._loaded.filter((item) => item.id !== id);
|
|
45
|
+
if (itemIndex < this._currentIndex) {
|
|
46
|
+
this._currentIndex--;
|
|
47
|
+
}
|
|
48
|
+
else if (itemIndex === this._currentIndex) {
|
|
49
|
+
if (this._currentIndex >= this._loaded.length) {
|
|
50
|
+
this._currentIndex = this._loaded.length - 1;
|
|
51
|
+
}
|
|
52
|
+
// Otherwise keep the same index (activates next item)
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
loadNext = async () => {
|
|
57
|
+
if (!this.canLoadNext) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (this.currentIndex < this.loaded.length - 1) {
|
|
61
|
+
++this._currentIndex;
|
|
62
|
+
await this.warmUpBuffer();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this._onEndReachedFn?.();
|
|
66
|
+
};
|
|
67
|
+
loadPrevious = async () => {
|
|
68
|
+
if (!this.canLoadPrevious) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
--this._currentIndex;
|
|
72
|
+
};
|
|
73
|
+
reset = () => {
|
|
74
|
+
this._loaded = [];
|
|
75
|
+
this._currentIndex = -1;
|
|
76
|
+
this._fetchDeferred = null;
|
|
77
|
+
this.warmUpBuffer();
|
|
78
|
+
};
|
|
79
|
+
ensureWarmedUp = async () => {
|
|
80
|
+
await this.warmUpBuffer();
|
|
81
|
+
};
|
|
82
|
+
initializeBuffer = async (options) => {
|
|
83
|
+
const { initialData } = options;
|
|
84
|
+
const handleInitialized = () => {
|
|
85
|
+
const startIndex = initialData.startIndex >= 0 ? initialData.startIndex : 0;
|
|
86
|
+
this._currentIndex = Math.min(startIndex, this._loaded.length - 1);
|
|
87
|
+
const hasCustomMediaIndex = initialData.startMediaIndex && initialData.startMediaIndex > 0;
|
|
88
|
+
const activeItem = this._loaded[this._currentIndex];
|
|
89
|
+
if (activeItem && hasCustomMediaIndex) {
|
|
90
|
+
activeItem.mediaIndex = initialData.startMediaIndex;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
this._loaded = initialData.prefetchedItems;
|
|
94
|
+
const considerInitialized = this._loaded.length > 0;
|
|
95
|
+
if (considerInitialized) {
|
|
96
|
+
handleInitialized();
|
|
97
|
+
}
|
|
98
|
+
await this.warmUpBuffer();
|
|
99
|
+
if (!considerInitialized) {
|
|
100
|
+
handleInitialized();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
warmUpBuffer = async () => {
|
|
104
|
+
// Early return if buffer is sufficient or already loading
|
|
105
|
+
if (this._fetchDeferred) {
|
|
106
|
+
return this._fetchDeferred.promise;
|
|
107
|
+
}
|
|
108
|
+
if (this._loaded.length >= this.currentIndex + BUFFER_SIZE) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this._fetchDeferred = new Deferred();
|
|
112
|
+
try {
|
|
113
|
+
const result = await this._loadMoreFn();
|
|
114
|
+
this._loaded = [...this._loaded, ...result];
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
this._fetchDeferred.resolve();
|
|
118
|
+
this._fetchDeferred = null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { DefaultChunksPlayerBuffer } from './default-chunks-player-buffer.svelte';
|
|
2
|
+
export { DefaultFeedPlayerBuffer } from './default-feed-player-buffer.svelte';
|
|
3
|
+
export { initBufferFromProvider } from './service';
|
|
4
|
+
export type { IChunksPlayerDataProvider, IFeedPlayerDataProvider, IPlayerBuffer, IPlayerDataProvider } from './types';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Utils } from '../../../core/utils';
|
|
2
|
+
import { DefaultChunksPlayerBuffer } from './default-chunks-player-buffer.svelte';
|
|
3
|
+
import { DefaultFeedPlayerBuffer } from './default-feed-player-buffer.svelte';
|
|
4
|
+
export const initBufferFromProvider = (provider) => {
|
|
5
|
+
switch (provider.kind) {
|
|
6
|
+
case 'feed':
|
|
7
|
+
return new DefaultFeedPlayerBuffer(provider);
|
|
8
|
+
case 'chunks':
|
|
9
|
+
return new DefaultChunksPlayerBuffer(provider);
|
|
10
|
+
default:
|
|
11
|
+
Utils.assertUnreachable(provider);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type IPlayerDataProvider<T extends WithId> = IFeedPlayerDataProvider<T> | IChunksPlayerDataProvider<T>;
|
|
2
|
+
export interface IFeedPlayerDataProvider<T extends WithId> {
|
|
3
|
+
kind: 'feed';
|
|
4
|
+
initialData: {
|
|
5
|
+
prefetchedItems: T[];
|
|
6
|
+
startIndex: number;
|
|
7
|
+
startMediaIndex?: number;
|
|
8
|
+
};
|
|
9
|
+
loadMore: () => Promise<T[]>;
|
|
10
|
+
onEndReached?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface IChunksPlayerDataProvider<TItem extends WithId, TChunk extends WithId = WithId> {
|
|
13
|
+
kind: 'chunks';
|
|
14
|
+
initialData: {
|
|
15
|
+
prefetchedChunks: TChunk[];
|
|
16
|
+
startItemIndex?: number;
|
|
17
|
+
};
|
|
18
|
+
loadMoreChunks: () => Promise<TChunk[]>;
|
|
19
|
+
loadChunkItems: (chunkId: string, continuationToken: string | null | undefined) => Promise<{
|
|
20
|
+
items: TItem[];
|
|
21
|
+
continuationToken: string | null;
|
|
22
|
+
}>;
|
|
23
|
+
onEndReached?: () => void;
|
|
24
|
+
}
|
|
25
|
+
export type IPlayerBuffer<T extends WithId> = IFeedPlayerBuffer<T> | IChunksPlayerBuffer<T>;
|
|
26
|
+
export interface IPlayerBufferBase<T extends WithId> {
|
|
27
|
+
readonly kind: 'feed' | 'chunks';
|
|
28
|
+
readonly current: TExtended<T> | null;
|
|
29
|
+
readonly loaded: TExtended<T>[];
|
|
30
|
+
readonly currentIndex: number;
|
|
31
|
+
readonly canLoadNext: boolean;
|
|
32
|
+
readonly canLoadPrevious: boolean;
|
|
33
|
+
readonly navigationDisabled: boolean;
|
|
34
|
+
readonly animationDuration: number;
|
|
35
|
+
loadNext: () => void;
|
|
36
|
+
loadPrevious: () => void;
|
|
37
|
+
reset: () => void;
|
|
38
|
+
ensureWarmedUp: () => Promise<void>;
|
|
39
|
+
tryActivateItemById: (id: string) => boolean;
|
|
40
|
+
removeItemById: (id: string) => void;
|
|
41
|
+
}
|
|
42
|
+
type TExtended<T> = T & {
|
|
43
|
+
mediaIndex?: number;
|
|
44
|
+
};
|
|
45
|
+
export interface IFeedPlayerBuffer<T extends WithId> extends IPlayerBufferBase<T> {
|
|
46
|
+
readonly kind: 'feed';
|
|
47
|
+
}
|
|
48
|
+
export interface IChunksPlayerBuffer<T extends WithId> extends IPlayerBufferBase<T> {
|
|
49
|
+
readonly kind: 'chunks';
|
|
50
|
+
}
|
|
51
|
+
export type WithId = {
|
|
52
|
+
id: string;
|
|
53
|
+
};
|
|
54
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TouchSynchronizer } from './touch-synchronizer';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TouchSynchronizer } from './touch-synchronizer';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class TouchSynchronizer {
|
|
2
|
+
private static listeners;
|
|
3
|
+
static touchStarted: (ref: HTMLElement) => void;
|
|
4
|
+
static touchMovePropagationBlocked: (ref: HTMLElement) => void;
|
|
5
|
+
static touchEndPropatationBlocked: (ref: HTMLElement) => void;
|
|
6
|
+
static touchEnded: (ref: HTMLElement) => void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class TouchSynchronizer {
|
|
2
|
+
static listeners = [];
|
|
3
|
+
static touchStarted = (ref) => {
|
|
4
|
+
TouchSynchronizer.listeners.push(ref);
|
|
5
|
+
};
|
|
6
|
+
static touchMovePropagationBlocked = (ref) => {
|
|
7
|
+
const otherListeners = TouchSynchronizer.listeners.filter((r) => r !== ref);
|
|
8
|
+
otherListeners.forEach((r) => {
|
|
9
|
+
r.dispatchEvent(new TouchEvent('touchend', { cancelable: false }));
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
static touchEndPropatationBlocked = (ref) => {
|
|
13
|
+
const otherListeners = TouchSynchronizer.listeners.filter((r) => r !== ref);
|
|
14
|
+
otherListeners.forEach((r) => {
|
|
15
|
+
r.dispatchEvent(new TouchEvent('touchend', { cancelable: false }));
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
static touchEnded = (ref) => {
|
|
19
|
+
TouchSynchronizer.listeners = TouchSynchronizer.listeners.filter((r) => r !== ref);
|
|
20
|
+
};
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamscloud/kit",
|
|
3
|
-
"version": "0.0.1-
|
|
3
|
+
"version": "0.0.1-1771075495185",
|
|
4
4
|
"author": "StreamsCloud",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -128,6 +128,22 @@
|
|
|
128
128
|
"types": "./dist/ui/media-playback/index.d.ts",
|
|
129
129
|
"svelte": "./dist/ui/media-playback/index.js"
|
|
130
130
|
},
|
|
131
|
+
"./ui/player/buttons": {
|
|
132
|
+
"types": "./dist/ui/player/buttons/index.d.ts",
|
|
133
|
+
"svelte": "./dist/ui/player/buttons/index.js"
|
|
134
|
+
},
|
|
135
|
+
"./ui/player/carousel": {
|
|
136
|
+
"types": "./dist/ui/player/carousel/index.d.ts",
|
|
137
|
+
"svelte": "./dist/ui/player/carousel/index.js"
|
|
138
|
+
},
|
|
139
|
+
"./ui/player/feed-slider": {
|
|
140
|
+
"types": "./dist/ui/player/feed-slider/index.d.ts",
|
|
141
|
+
"svelte": "./dist/ui/player/feed-slider/index.js"
|
|
142
|
+
},
|
|
143
|
+
"./ui/player/providers": {
|
|
144
|
+
"types": "./dist/ui/player/providers/index.d.ts",
|
|
145
|
+
"svelte": "./dist/ui/player/providers/index.js"
|
|
146
|
+
},
|
|
131
147
|
"./ui/progress": {
|
|
132
148
|
"types": "./dist/ui/progress/index.d.ts",
|
|
133
149
|
"svelte": "./dist/ui/progress/index.js"
|
|
@@ -184,12 +200,13 @@
|
|
|
184
200
|
"rfdc": "^1.4.1",
|
|
185
201
|
"svelte": "^5.50.0",
|
|
186
202
|
"svelte-sonner": "^1.0.7",
|
|
203
|
+
"wheel-gestures": "^2.2.48",
|
|
187
204
|
"yup": "^1.7.1"
|
|
188
205
|
},
|
|
189
206
|
"devDependencies": {
|
|
190
207
|
"@eslint/compat": "^2.0.2",
|
|
191
208
|
"@eslint/js": "^9.39.2",
|
|
192
|
-
"@fluentui/svg-icons": "^1.1.
|
|
209
|
+
"@fluentui/svg-icons": "^1.1.319",
|
|
193
210
|
"@popperjs/core": "^2.11.8",
|
|
194
211
|
"@sveltejs/package": "^2.5.7",
|
|
195
212
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
@@ -217,14 +234,15 @@
|
|
|
217
234
|
"publint": "^0.3.17",
|
|
218
235
|
"rfdc": "^1.4.1",
|
|
219
236
|
"sass": "^1.97.3",
|
|
220
|
-
"svelte": "^5.
|
|
221
|
-
"svelte-check": "^4.
|
|
237
|
+
"svelte": "^5.51.0",
|
|
238
|
+
"svelte-check": "^4.4.0",
|
|
222
239
|
"svelte-preprocess": "^6.0.3",
|
|
223
240
|
"svelte-sonner": "^1.0.7",
|
|
224
241
|
"typescript": "^5.9.3",
|
|
225
242
|
"typescript-eslint": "^8.55.0",
|
|
226
243
|
"vite": "^7.3.1",
|
|
227
|
-
"vite-tsconfig-paths": "^6.1.
|
|
244
|
+
"vite-tsconfig-paths": "^6.1.1",
|
|
245
|
+
"wheel-gestures": "^2.2.48",
|
|
228
246
|
"yup": "^1.7.1"
|
|
229
247
|
}
|
|
230
248
|
}
|