@scarlett-player/analytics 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hackney Enterprises Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,473 @@
1
+ # @scarlett-player/analytics
2
+
3
+ Analytics plugin for Scarlett Player that collects Quality of Experience (QoE) metrics and engagement data for live events and VOD content.
4
+
5
+ ## Features
6
+
7
+ - **Quality of Experience (QoE) Metrics**
8
+ - Startup time tracking
9
+ - Rebuffering detection and measurement
10
+ - Quality level changes
11
+ - Error tracking
12
+
13
+ - **Engagement Analytics**
14
+ - Watch time vs. play time
15
+ - Pause/seek behavior
16
+ - Completion rates
17
+ - Exit type detection
18
+
19
+ - **Automatic Tracking**
20
+ - Periodic heartbeat reporting
21
+ - Page visibility handling
22
+ - Persistent viewer identification
23
+ - Session management
24
+
25
+ - **Custom Events**
26
+ - Track business events (purchases, signups, etc.)
27
+ - Custom dimensions support
28
+ - Flexible data collection
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @scarlett-player/analytics
34
+ ```
35
+
36
+ Or with pnpm:
37
+
38
+ ```bash
39
+ pnpm add @scarlett-player/analytics
40
+ ```
41
+
42
+ ## Basic Usage
43
+
44
+ ```typescript
45
+ import { createPlayer } from '@scarlett-player/core';
46
+ import { hlsPlugin } from '@scarlett-player/hls';
47
+ import { createAnalyticsPlugin } from '@scarlett-player/analytics';
48
+ import { uiPlugin } from '@scarlett-player/ui';
49
+
50
+ const player = await createPlayer({
51
+ container: '#player',
52
+ src: 'https://example.com/stream.m3u8',
53
+ plugins: [
54
+ hlsPlugin(),
55
+ createAnalyticsPlugin({
56
+ beaconUrl: 'https://api.example.com/analytics/beacon',
57
+ videoId: 'event-123',
58
+ videoTitle: 'Live Fight Night',
59
+ isLive: true,
60
+ viewerId: user?.id,
61
+ viewerPlan: 'ppv',
62
+ }),
63
+ uiPlugin(),
64
+ ],
65
+ });
66
+ ```
67
+
68
+ ## Configuration
69
+
70
+ ### Required Options
71
+
72
+ ```typescript
73
+ {
74
+ beaconUrl: string; // Your analytics API endpoint
75
+ videoId: string; // Unique video identifier
76
+ }
77
+ ```
78
+
79
+ ### Optional Options
80
+
81
+ ```typescript
82
+ {
83
+ // Video metadata
84
+ videoTitle?: string;
85
+ videoSeries?: string;
86
+ videoDuration?: number;
87
+ isLive?: boolean;
88
+
89
+ // Viewer information
90
+ viewerId?: string; // Auto-generated if not provided
91
+ viewerPlan?: string; // 'free', 'ppv', 'subscriber', etc.
92
+
93
+ // Custom dimensions
94
+ customDimensions?: {
95
+ promoter?: string;
96
+ eventType?: string;
97
+ [key: string]: any;
98
+ };
99
+
100
+ // Behavior
101
+ heartbeatInterval?: number; // Default: 10000ms (10 seconds)
102
+ errorSampleRate?: number; // Default: 1.0 (100%)
103
+ disableInDev?: boolean; // Default: false
104
+ apiKey?: string; // Optional API key for authentication
105
+ }
106
+ ```
107
+
108
+ ## Events Tracked
109
+
110
+ ### Automatic Events
111
+
112
+ The plugin automatically tracks these events:
113
+
114
+ | Event | Description | Data |
115
+ |-------|-------------|------|
116
+ | `viewStart` | Player initialized | viewId, sessionId, environment |
117
+ | `playRequest` | User clicked play | timestamp |
118
+ | `videoStart` | First frame rendered | startupTime |
119
+ | `heartbeat` | Periodic update (10s default) | watchTime, playTime, QoE score |
120
+ | `pause` | Playback paused | currentTime, pauseCount |
121
+ | `seeking` | User seeked | seekTo, seekCount |
122
+ | `rebufferStart` | Buffering started | rebufferCount |
123
+ | `rebufferEnd` | Buffering ended | duration, totalRebufferTime |
124
+ | `qualityChange` | Quality level changed | bitrate, width, height |
125
+ | `error` | Error occurred | errorType, errorMessage, fatal |
126
+ | `viewEnd` | View session ended | all metrics, exitType, QoE score |
127
+
128
+ ### Exit Types
129
+
130
+ - `completed` - Video played to the end
131
+ - `abandoned` - User left before completion
132
+ - `error` - Fatal error stopped playback
133
+ - `background` - Tab/window was backgrounded
134
+
135
+ ## Custom Event Tracking
136
+
137
+ Track custom business events:
138
+
139
+ ```typescript
140
+ const analytics = player.plugins.get('analytics');
141
+
142
+ // Track PPV purchase
143
+ analytics.trackEvent('ppv_purchase', {
144
+ price: 49.99,
145
+ currency: 'USD',
146
+ paymentMethod: 'stripe',
147
+ });
148
+
149
+ // Track user signup
150
+ analytics.trackEvent('user_signup', {
151
+ plan: 'premium',
152
+ referral: 'social',
153
+ });
154
+
155
+ // Track engagement
156
+ analytics.trackEvent('share_clicked', {
157
+ platform: 'twitter',
158
+ });
159
+ ```
160
+
161
+ ## Beacon Payload Structure
162
+
163
+ Every beacon sent includes:
164
+
165
+ ```typescript
166
+ {
167
+ // Event info
168
+ event: string; // Event type
169
+ timestamp: number; // Unix timestamp
170
+
171
+ // View context
172
+ viewId: string; // Unique per playback attempt
173
+ sessionId: string; // Persists across views in session
174
+ viewerId: string; // Persists across sessions
175
+
176
+ // Video context
177
+ videoId: string;
178
+ videoTitle?: string;
179
+ isLive?: boolean;
180
+
181
+ // Player context
182
+ playerVersion: string;
183
+ playerName: string;
184
+
185
+ // Environment
186
+ browser: string; // 'Chrome', 'Safari', etc.
187
+ os: string; // 'Windows', 'macOS', etc.
188
+ deviceType: string; // 'desktop', 'mobile', 'tablet'
189
+ screenSize: string; // '1920x1080'
190
+ playerSize: string; // '1280x720'
191
+ connectionType: string; // '4g', 'wifi', etc.
192
+
193
+ // Custom dimensions
194
+ ...customDimensions,
195
+
196
+ // Event-specific data
197
+ ...eventData
198
+ }
199
+ ```
200
+
201
+ ## Quality of Experience (QoE) Score
202
+
203
+ The plugin calculates a QoE score (0-100) based on:
204
+
205
+ - **Success Score (30%)** - Did playback succeed without errors?
206
+ - **Startup Score (25%)** - How fast did video start?
207
+ - <1s: 100
208
+ - <2s: 85
209
+ - <4s: 70
210
+ - <8s: 50
211
+ - 8s+: 30
212
+
213
+ - **Smoothness Score (30%)** - How much rebuffering?
214
+ - <0.1% rebuffer ratio: 100
215
+ - <1%: 85
216
+ - <2%: 70
217
+ - <5%: 50
218
+ - 5%+: 30
219
+
220
+ - **Quality Score (15%)** - What bitrate was achieved?
221
+ - >4 Mbps (4K): 100
222
+ - >2 Mbps (1080p): 90
223
+ - >1 Mbps (720p): 75
224
+ - >500 Kbps (480p): 60
225
+ - Lower: 40
226
+
227
+ Access the score:
228
+
229
+ ```typescript
230
+ const analytics = player.plugins.get('analytics');
231
+ const qoeScore = analytics.getQoEScore(); // 0-100
232
+ ```
233
+
234
+ ## Metrics API
235
+
236
+ Get current session metrics:
237
+
238
+ ```typescript
239
+ const analytics = player.plugins.get('analytics');
240
+ const metrics = analytics.getMetrics();
241
+
242
+ console.log({
243
+ viewId: metrics.viewId,
244
+ watchTime: metrics.watchTime,
245
+ playTime: metrics.playTime,
246
+ rebufferCount: metrics.rebufferCount,
247
+ startupTime: metrics.startupTime,
248
+ qoeScore: analytics.getQoEScore(),
249
+ });
250
+ ```
251
+
252
+ ## Backend Integration
253
+
254
+ ### Endpoint Requirements
255
+
256
+ Your beacon endpoint should:
257
+
258
+ 1. Accept `POST` requests
259
+ 2. Handle `application/json` content type
260
+ 3. Support the `navigator.sendBeacon()` API (for reliability)
261
+ 4. Return quickly (don't block analytics on slow processing)
262
+
263
+ ### Example Express.js Handler
264
+
265
+ ```javascript
266
+ app.post('/analytics/beacon', async (req, res) => {
267
+ // Immediately respond
268
+ res.status(204).send();
269
+
270
+ // Process asynchronously
271
+ const event = req.body;
272
+
273
+ // Validate event
274
+ if (!event.viewId || !event.videoId) {
275
+ return;
276
+ }
277
+
278
+ // Store in database
279
+ await db.analyticsEvents.insert({
280
+ event_type: event.event,
281
+ view_id: event.viewId,
282
+ session_id: event.sessionId,
283
+ viewer_id: event.viewerId,
284
+ video_id: event.videoId,
285
+ timestamp: new Date(event.timestamp),
286
+ payload: event,
287
+ });
288
+
289
+ // Update aggregations if needed
290
+ if (event.event === 'viewEnd') {
291
+ await updateVideoStats(event.videoId, event);
292
+ }
293
+ });
294
+ ```
295
+
296
+ ### Laravel Example
297
+
298
+ ```php
299
+ Route::post('/analytics/beacon', function (Request $request) {
300
+ // Immediately respond
301
+ return response('', 204);
302
+ })->middleware(['throttle:1000,1']); // Rate limit
303
+
304
+ // Queue processing
305
+ Queue::push(new ProcessAnalyticsEvent($request->all()));
306
+ ```
307
+
308
+ ## TSP Integration Example
309
+
310
+ For The Stream Platform (TSP) live events:
311
+
312
+ ```typescript
313
+ const player = await createPlayer({
314
+ container: '#player',
315
+ src: event.streamUrl,
316
+ plugins: [
317
+ hlsPlugin({
318
+ lowLatencyMode: true,
319
+ }),
320
+ createAnalyticsPlugin({
321
+ beaconUrl: `${import.meta.env.VITE_API_URL}/analytics/beacon`,
322
+ apiKey: import.meta.env.VITE_ANALYTICS_KEY,
323
+
324
+ // Video context
325
+ videoId: event.id,
326
+ videoTitle: event.title,
327
+ isLive: true,
328
+
329
+ // Viewer context
330
+ viewerId: user?.id,
331
+ viewerPlan: user?.subscription ? 'subscriber' : 'ppv',
332
+
333
+ // Custom dimensions for TSP
334
+ customDimensions: {
335
+ eventType: 'fight',
336
+ promoter: event.promoter,
337
+ isPpv: event.isPpv,
338
+ price: event.price,
339
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
340
+ },
341
+
342
+ // Behavior
343
+ heartbeatInterval: 15000, // 15s for live
344
+ errorSampleRate: 1.0, // Track all errors
345
+ disableInDev: false, // Track even in dev
346
+ }),
347
+ uiPlugin(),
348
+ ],
349
+ });
350
+
351
+ // Track PPV purchase
352
+ if (purchaseSuccessful) {
353
+ const analytics = player.plugins.get('analytics');
354
+ analytics.trackEvent('ppv_purchase', {
355
+ price: event.price,
356
+ currency: 'USD',
357
+ paymentMethod: paymentData.method,
358
+ promotionCode: paymentData.promoCode,
359
+ });
360
+ }
361
+ ```
362
+
363
+ ## Privacy Considerations
364
+
365
+ The plugin respects user privacy:
366
+
367
+ - **Anonymous Tracking**: If no `viewerId` is provided, generates anonymous IDs stored in localStorage
368
+ - **No PII**: Doesn't collect personally identifiable information
369
+ - **User Agent Only**: Uses standard browser APIs for environment detection
370
+ - **Opt-out Support**: Can disable with `disableInDev` or custom logic
371
+
372
+ ### GDPR Compliance Example
373
+
374
+ ```typescript
375
+ const hasAnalyticsConsent = cookieConsent.analytics;
376
+
377
+ const player = await createPlayer({
378
+ container: '#player',
379
+ plugins: [
380
+ hlsPlugin(),
381
+ // Only load analytics if user consented
382
+ ...(hasAnalyticsConsent ? [
383
+ createAnalyticsPlugin({
384
+ beaconUrl: API_URL,
385
+ videoId: video.id,
386
+ })
387
+ ] : []),
388
+ uiPlugin(),
389
+ ],
390
+ });
391
+ ```
392
+
393
+ ## Testing
394
+
395
+ The plugin includes comprehensive tests. Run them:
396
+
397
+ ```bash
398
+ npm test
399
+ ```
400
+
401
+ For coverage:
402
+
403
+ ```bash
404
+ npm run test:coverage
405
+ ```
406
+
407
+ ### Mock Beacon for Testing
408
+
409
+ ```typescript
410
+ import { createAnalyticsPlugin } from '@scarlett-player/analytics';
411
+
412
+ const beacons = [];
413
+ const mockBeacon = (url, payload) => {
414
+ beacons.push(payload);
415
+ };
416
+
417
+ const plugin = createAnalyticsPlugin({
418
+ beaconUrl: 'http://test',
419
+ videoId: 'test-123',
420
+ customBeacon: mockBeacon, // Use mock instead of real beacon
421
+ });
422
+
423
+ // ... test your code ...
424
+
425
+ expect(beacons).toHaveLength(1);
426
+ expect(beacons[0].event).toBe('viewStart');
427
+ ```
428
+
429
+ ## Performance
430
+
431
+ The plugin is designed for minimal performance impact:
432
+
433
+ - **Async Beacons**: Uses `navigator.sendBeacon()` for non-blocking sends
434
+ - **Efficient Timers**: Single heartbeat interval per player
435
+ - **Lazy Calculation**: QoE score calculated only when needed
436
+ - **Memory Efficient**: Limits error history and bitrate tracking
437
+
438
+ ## Troubleshooting
439
+
440
+ ### Beacons Not Sending
441
+
442
+ 1. Check browser console for CORS errors
443
+ 2. Verify `beaconUrl` is correct
444
+ 3. Check network tab for beacon requests
445
+ 4. Ensure endpoint accepts POST with JSON
446
+
447
+ ### Missing Events
448
+
449
+ 1. Verify plugin is loaded before playback
450
+ 2. Check event subscriptions in browser DevTools
451
+ 3. Enable debug logging: `disableInDev: false`
452
+
453
+ ### Incorrect Metrics
454
+
455
+ 1. Verify player state is correct
456
+ 2. Check for multiple plugin instances
457
+ 3. Ensure cleanup on destroy
458
+
459
+ ## License
460
+
461
+ MIT
462
+
463
+ ## Support
464
+
465
+ For issues and questions:
466
+ - GitHub: https://github.com/Hackney-Enterprises-Inc/scarlett-player/issues
467
+ - Docs: https://scarlettplayer.com
468
+
469
+ ## Related Packages
470
+
471
+ - [@scarlett-player/core](../core) - Core player
472
+ - [@scarlett-player/hls](../hls) - HLS provider
473
+ - [@scarlett-player/ui](../ui) - UI components