@streamscloud/embeddable 2.1.4 → 2.1.6
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/streams/stream-player/cmp.stream-player.svelte +90 -10
- package/dist/streams/stream-player/cmp.stream-player.svelte.d.ts +1 -2
- package/dist/streams/stream-player/controls.svelte +6 -1
- package/dist/streams/stream-player/index.js +21 -0
- package/dist/streams/stream-player/operations.generated.d.ts +4 -0
- package/dist/streams/stream-player/operations.generated.js +6 -2
- package/dist/streams/stream-player/operations.graphql +2 -0
- package/dist/ui/icon/cmp.icon.svelte +11 -11
- package/dist/ui/player/cmp.player-slider.svelte +14 -1
- package/dist/ui/player/cmp.player-slider.svelte.d.ts +4 -0
- package/package.json +3 -2
|
@@ -21,14 +21,32 @@ import { default as Overview } from './stream-overview.svelte';
|
|
|
21
21
|
import { StreamPlayerBuffer } from './stream-player-buffer.svelte';
|
|
22
22
|
import { StreamPlayerLocalization } from './stream-player-localization.svelte';
|
|
23
23
|
import { StreamPlayerUiManager } from './ui-manager.svelte';
|
|
24
|
-
import {
|
|
24
|
+
import { onMount } from 'svelte';
|
|
25
|
+
import { AppEventsTracker } from '@streamscloud/streams-analytics-collector';
|
|
25
26
|
let { streamId, graphql, localization: localizationInit, on } = $props();
|
|
26
27
|
const localization = $derived(new StreamPlayerLocalization(localizationInit));
|
|
27
28
|
let model = $state(null);
|
|
28
29
|
let buffer = $state.raw(null);
|
|
29
30
|
let loading = $state(true);
|
|
30
31
|
let activePageId = $derived.by(() => { var _a, _b; return (_b = (_a = buffer === null || buffer === void 0 ? void 0 : buffer.current) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : ''; });
|
|
32
|
+
let totalEngagementTimeSeconds = 0;
|
|
33
|
+
let inactiveTimeSeconds = 10; // // Mark as inactive after 10 seconds of no activity
|
|
34
|
+
let isActive = true;
|
|
35
|
+
let activityTimeout = null;
|
|
36
|
+
let trackingInterval = null;
|
|
37
|
+
let maxPageIndexViewed = 0;
|
|
31
38
|
const uiManager = new StreamPlayerUiManager();
|
|
39
|
+
const resetInactivityTimer = () => {
|
|
40
|
+
if (!isActive) {
|
|
41
|
+
isActive = true;
|
|
42
|
+
}
|
|
43
|
+
if (activityTimeout) {
|
|
44
|
+
clearTimeout(activityTimeout);
|
|
45
|
+
}
|
|
46
|
+
activityTimeout = setTimeout(() => {
|
|
47
|
+
isActive = false;
|
|
48
|
+
}, inactiveTimeSeconds * 1000);
|
|
49
|
+
};
|
|
32
50
|
onMount(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
51
|
var _a, _b, _c, _d;
|
|
34
52
|
uiManager.overviewCollapsed = window && window.innerWidth < window.innerHeight;
|
|
@@ -40,20 +58,48 @@ onMount(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
40
58
|
return;
|
|
41
59
|
}
|
|
42
60
|
(_c = on === null || on === void 0 ? void 0 : on.streamActivated) === null || _c === void 0 ? void 0 : _c.call(on, {
|
|
61
|
+
ownerId: streamPayload.data.stream.ownerProfile.id,
|
|
43
62
|
title: streamPayload.data.stream.title,
|
|
44
63
|
image: ((_d = streamPayload.data.stream.cover) === null || _d === void 0 ? void 0 : _d.url) || null
|
|
45
64
|
});
|
|
46
65
|
// start tracking the stream
|
|
47
66
|
model = mapStreamPlayerModel(streamPayload.data.stream);
|
|
48
67
|
buffer = new StreamPlayerBuffer({ graphql, streamId });
|
|
68
|
+
AppEventsTracker.trackStreamView(streamPayload.data.stream.id);
|
|
69
|
+
startActivityTracking();
|
|
49
70
|
}
|
|
50
71
|
finally {
|
|
51
72
|
loading = false;
|
|
52
73
|
}
|
|
53
74
|
}));
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
const startActivityTracking = () => {
|
|
76
|
+
trackingInterval = setInterval(() => {
|
|
77
|
+
if (isActive) {
|
|
78
|
+
totalEngagementTimeSeconds += 1;
|
|
79
|
+
}
|
|
80
|
+
}, 1000);
|
|
81
|
+
window.addEventListener('mousemove', resetInactivityTimer);
|
|
82
|
+
window.addEventListener('mousedown', resetInactivityTimer);
|
|
83
|
+
window.addEventListener('keypress', resetInactivityTimer);
|
|
84
|
+
window.addEventListener('touchstart', resetInactivityTimer);
|
|
85
|
+
window.addEventListener('scroll', resetInactivityTimer);
|
|
86
|
+
resetInactivityTimer();
|
|
87
|
+
};
|
|
88
|
+
const stopActivityTracking = () => {
|
|
89
|
+
window.removeEventListener('mousemove', resetInactivityTimer);
|
|
90
|
+
window.removeEventListener('mousedown', resetInactivityTimer);
|
|
91
|
+
window.removeEventListener('keypress', resetInactivityTimer);
|
|
92
|
+
window.removeEventListener('touchstart', resetInactivityTimer);
|
|
93
|
+
window.removeEventListener('scroll', resetInactivityTimer);
|
|
94
|
+
if (activityTimeout) {
|
|
95
|
+
clearTimeout(activityTimeout);
|
|
96
|
+
activityTimeout = null;
|
|
97
|
+
}
|
|
98
|
+
if (trackingInterval) {
|
|
99
|
+
clearInterval(trackingInterval);
|
|
100
|
+
trackingInterval = null;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
57
103
|
const handleChangePage = (index) => {
|
|
58
104
|
if (!buffer) {
|
|
59
105
|
return;
|
|
@@ -69,6 +115,35 @@ const handleDimensionsChanged = (dimensions) => {
|
|
|
69
115
|
viewTotalWidth: dimensions.totalWidth
|
|
70
116
|
});
|
|
71
117
|
};
|
|
118
|
+
const onPageActivated = (id) => {
|
|
119
|
+
const page = buffer === null || buffer === void 0 ? void 0 : buffer.loaded.find((x) => x.id === id);
|
|
120
|
+
if (page) {
|
|
121
|
+
AppEventsTracker.trackStreamPageView(id, streamId);
|
|
122
|
+
const currentIndex = buffer === null || buffer === void 0 ? void 0 : buffer.loaded.findIndex((p) => p.id === id);
|
|
123
|
+
if (currentIndex !== undefined && currentIndex > maxPageIndexViewed) {
|
|
124
|
+
maxPageIndexViewed = currentIndex;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const onProductCardClick = (productId) => {
|
|
129
|
+
AppEventsTracker.trackStreamProductClicked(productId, streamId);
|
|
130
|
+
};
|
|
131
|
+
const onPlayerClose = () => {
|
|
132
|
+
var _a;
|
|
133
|
+
stopActivityTracking();
|
|
134
|
+
AppEventsTracker.trackStreamEngagementTime(streamId, totalEngagementTimeSeconds);
|
|
135
|
+
if (buffer && buffer.loaded.length > 0) {
|
|
136
|
+
let scrollDepth = Math.round(((maxPageIndexViewed + 1) / buffer.loaded.length) * 100);
|
|
137
|
+
AppEventsTracker.trackStreamScrollDepth(streamId, scrollDepth);
|
|
138
|
+
}
|
|
139
|
+
(_a = on === null || on === void 0 ? void 0 : on.closePlayer) === null || _a === void 0 ? void 0 : _a.call(on);
|
|
140
|
+
};
|
|
141
|
+
const onPageDeactivated = (itemId) => {
|
|
142
|
+
AppEventsTracker.reportPageVideoViews(itemId, streamId);
|
|
143
|
+
};
|
|
144
|
+
const onProgress = (pageId, videoId, progress) => {
|
|
145
|
+
AppEventsTracker.trackShortVideoProgress(pageId, videoId, progress, streamId);
|
|
146
|
+
};
|
|
72
147
|
</script>
|
|
73
148
|
|
|
74
149
|
<div class="stream-player-container">
|
|
@@ -79,20 +154,25 @@ const handleDimensionsChanged = (dimensions) => {
|
|
|
79
154
|
{#if buffer && model}
|
|
80
155
|
<SpotlightLayout ratio={9 / 16} on={{ dimensionsChanged: handleDimensionsChanged }}>
|
|
81
156
|
<div class="stream-player__content">
|
|
82
|
-
<PlayerSlider
|
|
157
|
+
<PlayerSlider
|
|
158
|
+
buffer={buffer}
|
|
159
|
+
on={{
|
|
160
|
+
itemActivated: (e) => onPageActivated(e),
|
|
161
|
+
itemDeactivated: (e) => onPageDeactivated(e)
|
|
162
|
+
}}>
|
|
83
163
|
{#snippet children({ item })}
|
|
84
164
|
{#if item.type === 'general'}
|
|
85
165
|
<StreamPageViewer
|
|
86
166
|
page={item}
|
|
87
167
|
on={{
|
|
88
|
-
progress: (videoId: string, progress: number) =>
|
|
89
|
-
productClick: (productId: string) =>
|
|
168
|
+
progress: (videoId: string, progress: number) => onProgress(item.id, videoId, progress),
|
|
169
|
+
productClick: (productId: string) => onProductCardClick(productId)
|
|
90
170
|
}} />
|
|
91
171
|
{:else if item.type === 'short-video'}
|
|
92
172
|
<ShortVideoViewer
|
|
93
173
|
model={mapToShortVideoViewerModel(item.shortVideo)}
|
|
94
174
|
on={{
|
|
95
|
-
progress: (progress
|
|
175
|
+
progress: (progress) => onProgress(item.id, item.shortVideo.id, progress)
|
|
96
176
|
}}
|
|
97
177
|
autoplay="on-appearance"
|
|
98
178
|
showAttachments={uiManager.showShortVideoOverlay} />
|
|
@@ -115,8 +195,8 @@ const handleDimensionsChanged = (dimensions) => {
|
|
|
115
195
|
uiManager={uiManager}
|
|
116
196
|
localization={localization}
|
|
117
197
|
on={{
|
|
118
|
-
closePlayer: () =>
|
|
119
|
-
productClick: (productId: string) =>
|
|
198
|
+
closePlayer: () => onPlayerClose(),
|
|
199
|
+
productClick: (productId: string) => onProductCardClick(productId)
|
|
120
200
|
}} />
|
|
121
201
|
{/if}
|
|
122
202
|
</div>
|
|
@@ -7,11 +7,10 @@ type Props = {
|
|
|
7
7
|
on?: {
|
|
8
8
|
closePlayer?: () => void;
|
|
9
9
|
streamActivated?: (data: {
|
|
10
|
+
ownerId: string;
|
|
10
11
|
title: string;
|
|
11
12
|
image: string | null;
|
|
12
13
|
}) => void;
|
|
13
|
-
productClick?: (productId: string) => void;
|
|
14
|
-
progress?: (videoId: string, progress: number) => void;
|
|
15
14
|
};
|
|
16
15
|
};
|
|
17
16
|
declare const Cmp: import("svelte").Component<Props, {}, "">;
|
|
@@ -40,7 +40,12 @@ const shortVideo = $derived(((_a = buffer.current) === null || _a === void 0 ? v
|
|
|
40
40
|
<div class="stream-player-controls__right">
|
|
41
41
|
{#if shortVideo && shortVideo.products}
|
|
42
42
|
<div class="stream-player-controls__short-video-attachments">
|
|
43
|
-
<ShortVideoViewerAttachments
|
|
43
|
+
<ShortVideoViewerAttachments
|
|
44
|
+
shortVideo={shortVideo}
|
|
45
|
+
localization={localization.shortVideoDetailsLocalization?.attachmentsLocalization}
|
|
46
|
+
on={{
|
|
47
|
+
productClick: on.productClick
|
|
48
|
+
}} />
|
|
44
49
|
</div>
|
|
45
50
|
{/if}
|
|
46
51
|
<div class="stream-player-controls__controls">
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { createLocalGQLClient } from '../../core/graphql';
|
|
2
2
|
import { ShadowHost } from '../../ui/shadow-dom';
|
|
3
3
|
import { default as StreamPlayer } from './cmp.stream-player.svelte';
|
|
4
|
+
import { AppEventsTracker } from '@streamscloud/streams-analytics-collector';
|
|
4
5
|
import { mount } from 'svelte';
|
|
6
|
+
/**
|
|
7
|
+
* Key used for storing the profile ID in local storage
|
|
8
|
+
*/
|
|
9
|
+
const PROFILE_ID_STORAGE_KEY = 'streamscloud_profile_id';
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves the profile ID from localStorage or generates a new one if it doesn't exist
|
|
12
|
+
* @returns The profile ID to use for analytics tracking
|
|
13
|
+
*/
|
|
14
|
+
function getOrCreateProfileId() {
|
|
15
|
+
const storedProfileId = localStorage.getItem(PROFILE_ID_STORAGE_KEY);
|
|
16
|
+
if (!storedProfileId) {
|
|
17
|
+
const newProfileId = crypto.randomUUID();
|
|
18
|
+
localStorage.setItem(PROFILE_ID_STORAGE_KEY, newProfileId);
|
|
19
|
+
return newProfileId;
|
|
20
|
+
}
|
|
21
|
+
return storedProfileId;
|
|
22
|
+
}
|
|
5
23
|
/**
|
|
6
24
|
* Opens the stream player modal with the specified stream details.
|
|
7
25
|
*
|
|
@@ -28,6 +46,8 @@ import { mount } from 'svelte';
|
|
|
28
46
|
export const openStreamPlayer = (init) => {
|
|
29
47
|
const { streamId, graphqlUrl, localization } = init;
|
|
30
48
|
const shadowHost = new ShadowHost({ onClosed: () => init.on?.playerClosed?.() });
|
|
49
|
+
AppEventsTracker.setEndpoint(graphqlUrl);
|
|
50
|
+
AppEventsTracker.setProfileId(getOrCreateProfileId());
|
|
31
51
|
mount(StreamPlayer, {
|
|
32
52
|
target: shadowHost.shadowRoot,
|
|
33
53
|
props: {
|
|
@@ -36,6 +56,7 @@ export const openStreamPlayer = (init) => {
|
|
|
36
56
|
localization,
|
|
37
57
|
on: {
|
|
38
58
|
streamActivated: (data) => {
|
|
59
|
+
AppEventsTracker.setOrganizationId(data.ownerId);
|
|
39
60
|
if (init.on?.streamActivated) {
|
|
40
61
|
init.on.streamActivated({ title: data.title, image: data.image });
|
|
41
62
|
}
|
|
@@ -17,6 +17,8 @@ export type GetStreamQuery = {
|
|
|
17
17
|
ownerProfile: {
|
|
18
18
|
image: string | null;
|
|
19
19
|
name: string;
|
|
20
|
+
id: string;
|
|
21
|
+
type: SchemaTypes.ProfileType;
|
|
20
22
|
};
|
|
21
23
|
availableFrom: {
|
|
22
24
|
image: string;
|
|
@@ -54,6 +56,8 @@ export type StreamPlayerPayloadFragment = {
|
|
|
54
56
|
ownerProfile: {
|
|
55
57
|
image: string | null;
|
|
56
58
|
name: string;
|
|
59
|
+
id: string;
|
|
60
|
+
type: SchemaTypes.ProfileType;
|
|
57
61
|
};
|
|
58
62
|
availableFrom: {
|
|
59
63
|
image: string;
|
|
@@ -25,7 +25,9 @@ export const StreamPlayerPayloadFragmentDoc = {
|
|
|
25
25
|
kind: 'SelectionSet',
|
|
26
26
|
selections: [
|
|
27
27
|
{ kind: 'Field', name: { kind: 'Name', value: 'image' } },
|
|
28
|
-
{ kind: 'Field', name: { kind: 'Name', value: 'name' } }
|
|
28
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
|
29
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
|
30
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'type' } }
|
|
29
31
|
]
|
|
30
32
|
}
|
|
31
33
|
},
|
|
@@ -105,7 +107,9 @@ export const GetStreamDocument = {
|
|
|
105
107
|
kind: 'SelectionSet',
|
|
106
108
|
selections: [
|
|
107
109
|
{ kind: 'Field', name: { kind: 'Name', value: 'image' } },
|
|
108
|
-
{ kind: 'Field', name: { kind: 'Name', value: 'name' } }
|
|
110
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
|
111
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
|
112
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'type' } }
|
|
109
113
|
]
|
|
110
114
|
}
|
|
111
115
|
},
|
|
@@ -13,20 +13,20 @@ const fillSvg = (node) => {
|
|
|
13
13
|
};
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
|
-
<div
|
|
16
|
+
<div
|
|
17
|
+
class="icon"
|
|
18
|
+
style="display: contents"
|
|
19
|
+
class:icon--white={color === IconColor.White}
|
|
20
|
+
class:icon--text={color === IconColor.Text}
|
|
21
|
+
class:icon--gray={color === IconColor.Gray}
|
|
22
|
+
class:icon--green={color === IconColor.Green}
|
|
23
|
+
class:icon--red={color === IconColor.Red}
|
|
24
|
+
class:icon--orange={color === IconColor.Orange}
|
|
25
|
+
class:icon--blue={color === IconColor.Blue}>
|
|
17
26
|
{#if src.startsWith('<svg')}
|
|
18
27
|
{@html src}
|
|
19
28
|
{:else}
|
|
20
|
-
<svg
|
|
21
|
-
class="icon"
|
|
22
|
-
use:fillSvg
|
|
23
|
-
class:icon--white={color === IconColor.White}
|
|
24
|
-
class:icon--text={color === IconColor.Text}
|
|
25
|
-
class:icon--gray={color === IconColor.Gray}
|
|
26
|
-
class:icon--green={color === IconColor.Green}
|
|
27
|
-
class:icon--red={color === IconColor.Red}
|
|
28
|
-
class:icon--orange={color === IconColor.Orange}
|
|
29
|
-
class:icon--blue={color === IconColor.Blue}></svg>
|
|
29
|
+
<svg use:fillSvg></svg>
|
|
30
30
|
{/if}
|
|
31
31
|
</div>
|
|
32
32
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">import { Utils } from '../../core/utils';
|
|
2
2
|
import { isScrollingPrevented } from './prevent-slider-scroll';
|
|
3
3
|
import { onDestroy, onMount } from 'svelte';
|
|
4
|
-
let { buffer, children } = $props();
|
|
4
|
+
let { buffer, children, on } = $props();
|
|
5
5
|
let slidesRef;
|
|
6
6
|
let sliderHeight = $state(0);
|
|
7
7
|
let swipeTransition = $state(0);
|
|
@@ -17,6 +17,8 @@ const onKeyPress = (e) => {
|
|
|
17
17
|
buffer.loadNext();
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
+
// Notify the parent component that the active item is about to change.
|
|
21
|
+
let active = null;
|
|
20
22
|
$effect(() => {
|
|
21
23
|
if (buffer.currentIndex >= 0 && activeIndex >= 0 && Math.abs(activeIndex - buffer.currentIndex) === 1) {
|
|
22
24
|
slidesRef.classList.toggle('animate', true);
|
|
@@ -85,6 +87,7 @@ onMount(() => {
|
|
|
85
87
|
return;
|
|
86
88
|
}
|
|
87
89
|
slidesRef.classList.toggle('animate', false);
|
|
90
|
+
notifyOnItemChanged();
|
|
88
91
|
});
|
|
89
92
|
sliderHeight = slidesRef.clientHeight;
|
|
90
93
|
resizeObserver = new ResizeObserver(() => {
|
|
@@ -92,6 +95,16 @@ onMount(() => {
|
|
|
92
95
|
});
|
|
93
96
|
resizeObserver.observe(slidesRef);
|
|
94
97
|
});
|
|
98
|
+
const notifyOnItemChanged = () => {
|
|
99
|
+
var _a, _b, _c;
|
|
100
|
+
if (active && ((_a = buffer.current) === null || _a === void 0 ? void 0 : _a.id) !== active.id) {
|
|
101
|
+
(_b = on === null || on === void 0 ? void 0 : on.itemDeactivated) === null || _b === void 0 ? void 0 : _b.call(on, active.id);
|
|
102
|
+
}
|
|
103
|
+
active = buffer.current;
|
|
104
|
+
if (active) {
|
|
105
|
+
(_c = on === null || on === void 0 ? void 0 : on.itemActivated) === null || _c === void 0 ? void 0 : _c.call(on, active.id);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
95
108
|
onDestroy(() => {
|
|
96
109
|
if (resizeObserver) {
|
|
97
110
|
resizeObserver.disconnect();
|
|
@@ -5,6 +5,10 @@ declare class __sveltets_Render<T extends {
|
|
|
5
5
|
}> {
|
|
6
6
|
props(): {
|
|
7
7
|
buffer: IPlayerBuffer<T>;
|
|
8
|
+
on?: {
|
|
9
|
+
itemDeactivated?: ((e: string) => void) | undefined;
|
|
10
|
+
itemActivated?: ((e: string) => void) | undefined;
|
|
11
|
+
} | undefined;
|
|
8
12
|
children: Snippet<[{
|
|
9
13
|
item: T;
|
|
10
14
|
active?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamscloud/embeddable",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"author": "StreamsCloud",
|
|
5
5
|
"repository": "https://github.com/StreamsCloud/streamscloud-frontend-packages.git",
|
|
6
6
|
"type": "module",
|
|
@@ -95,7 +95,8 @@
|
|
|
95
95
|
"@svelte-put/inline-svg": "^4.0.1",
|
|
96
96
|
"@urql/core": "^5.1.1",
|
|
97
97
|
"mobile-detect": "^1.4.5",
|
|
98
|
-
"svelte": "^5.25.3"
|
|
98
|
+
"svelte": "^5.25.3",
|
|
99
|
+
"@streamscloud/streams-analytics-collector": "latest"
|
|
99
100
|
},
|
|
100
101
|
"devDependencies": {
|
|
101
102
|
"@eslint/compat": "^1.2.9",
|