@lordicon/web 1.0.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/src/player.ts ADDED
@@ -0,0 +1,807 @@
1
+ import lottie, { AnimationConfig } from '@lordicon/internal';
2
+ import { ColorMap, EventHandler, EventName, IconProperties, IconState, LegacyIconProperties, LottieAnimationInstance, LottieData, LottieProperty, PlaybackDirection, Stroke } from './interfaces';
3
+ import { extractLottieProperties, resetLottieProperties, tupleColorToHex, updateLottieProperties } from './lottie';
4
+ import { parseStroke } from './parsers';
5
+ import { deepClone, get, isNil, set } from './utils';
6
+
7
+ /**
8
+ * LottieOptions type represents the configuration options for the Lottie player.
9
+ */
10
+ export type LottieOptions = Omit<AnimationConfig, 'container'>;
11
+
12
+ /**
13
+ * Default options used by the Player.
14
+ * These options are passed to the underlying Lottie player.
15
+ */
16
+ const DEFAULT_LOTTIE_WEB_OPTIONS: Omit<AnimationConfig, 'container'> = {
17
+ loop: false,
18
+ autoplay: false,
19
+ rendererSettings: {
20
+ preserveAspectRatio: "xMidYMid meet",
21
+ progressiveLoad: true,
22
+ hideOnTransparent: true,
23
+ },
24
+ }
25
+
26
+ /**
27
+ * Supported state flags for icons.
28
+ * Currently only 'default' is supported.
29
+ */
30
+ const SUPPORTED_STATE_FLAGS = ['default'];
31
+
32
+ /**
33
+ * Creates a Proxy for convenient color manipulation.
34
+ * Allows direct access to color properties by name.
35
+ *
36
+ * Example:
37
+ * player.colors.primary = '#ff0000';
38
+ * delete player.colors.secondary;
39
+ */
40
+ function createColorsProxy(this: Player) {
41
+ return new Proxy<Player>(this, {
42
+ set: (target, property, value, _receiver): boolean => {
43
+ if (typeof property === 'string') {
44
+ if (value) {
45
+ updateLottieProperties(
46
+ this.lottieInstance,
47
+ this.lottieProperties.filter(c => c.type === 'color' && c.name === property),
48
+ value,
49
+ );
50
+ } else {
51
+ resetLottieProperties(
52
+ this.lottieInstance,
53
+ this.lottieProperties.filter(c => c.type === 'color' && c.name === property),
54
+ );
55
+ }
56
+ target.refresh();
57
+ }
58
+ return true;
59
+ },
60
+ get: (target, property, _receiver) => {
61
+ for (const current of target.lottieProperties) {
62
+ if (current.type == 'color' && typeof property === 'string' && property == current.name) {
63
+ const data = get(this.lottieInstance, current.path);
64
+ if (data) {
65
+ return tupleColorToHex(data);
66
+ }
67
+ }
68
+ }
69
+ return undefined;
70
+ },
71
+ deleteProperty: (target, property) => {
72
+ if (typeof property === 'string') {
73
+ resetLottieProperties(
74
+ this.lottieInstance,
75
+ this.lottieProperties.filter(c => c.type === 'color' && c.name === property),
76
+ );
77
+ target.refresh();
78
+ }
79
+ return true;
80
+ },
81
+ ownKeys: (target) => {
82
+ return target.lottieProperties.filter(c => c.type == 'color').map(c => c.name);
83
+ },
84
+ has: (target, property) => {
85
+ for (const current of target.lottieProperties) {
86
+ if (current.type == 'color' && typeof property === 'string' && property == current.name) {
87
+ return true;
88
+ }
89
+ }
90
+ return false;
91
+ },
92
+ getOwnPropertyDescriptor: (_target) => {
93
+ return {
94
+ enumerable: true,
95
+ configurable: true,
96
+ };
97
+ },
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Player class for controlling and customizing Lottie-based icons.
103
+ */
104
+ export class Player {
105
+ protected _container: HTMLElement;
106
+ protected _iconData: any;
107
+ protected _initialProperties: IconProperties & LegacyIconProperties;
108
+ protected _lottieInstance?: LottieAnimationInstance;
109
+ protected _ready: boolean = false;
110
+ protected _colorsProxy?: any;
111
+ protected _direction: PlaybackDirection = 1;
112
+ protected _speed: number = 1;
113
+ protected _lottieProperties?: LottieProperty[];
114
+ protected _eventHandlers: any = {};
115
+
116
+ protected _state?: IconState;
117
+ protected _availableStates: IconState[];
118
+
119
+ /**
120
+ * Creates a new Player instance.
121
+ * @param container The DOM element where the animation will be rendered.
122
+ * @param data Lottie animation data.
123
+ * @param properties Initial icon properties (colors, stroke, state, etc.).
124
+ * @param options Additional options (e.g., autoInit).
125
+ */
126
+ constructor(
127
+ container: HTMLElement,
128
+ data: LottieData,
129
+ properties?: IconProperties & LegacyIconProperties,
130
+ options: { autoInit?: boolean } = { autoInit: true },
131
+ ) {
132
+ this._container = container;
133
+ this._iconData = data;
134
+ this._initialProperties = properties || {};
135
+
136
+ // Parse available states from Lottie markers.
137
+ this._availableStates = (data.markers || []).map((c: any) => {
138
+ const parts: string[] = c.cm.split(':');
139
+
140
+ const newState: IconState = {
141
+ time: c.tm,
142
+ duration: c.dr,
143
+ name: '',
144
+ default: false,
145
+ params: [],
146
+ };
147
+
148
+ // Read state flags from the first part of the marker name.
149
+ while (SUPPORTED_STATE_FLAGS.includes(parts[0])) {
150
+ switch (parts[0]) {
151
+ case 'default':
152
+ newState.default = true;
153
+ break;
154
+ default:
155
+ throw new Error(`Unsupported state flag: ${parts[0]}`);
156
+ }
157
+
158
+ parts.shift();
159
+ }
160
+
161
+ // Parse state name and parameters from the remaining parts.
162
+ newState.name = parts[0];
163
+ newState.params = parts.slice(1, parts.length);
164
+
165
+ // Set initial state if it matches, or use default if not specified.
166
+ if (newState.name === this._initialProperties.state) {
167
+ this._state = newState;
168
+ } else if (newState.default && isNil(this._initialProperties.state)) {
169
+ this._state = newState;
170
+ }
171
+
172
+ return newState;
173
+ }).filter((c: IconState) => c.duration > 0);
174
+
175
+ // Handle new and legacy icon files.
176
+ if (this._availableStates.length) {
177
+ // Remove unsupported stroke values.
178
+ if (this._initialProperties.stroke && ![1, 2, 3, 'light', 'regular', 'bold'].includes(this._initialProperties.stroke)) {
179
+ delete this._initialProperties.stroke;
180
+ }
181
+
182
+ // Fallback to default state if initial is invalid.
183
+ if (this._initialProperties.state && !this._state) {
184
+ this._state = this._availableStates.filter(c => c.default)[0];
185
+ }
186
+ }
187
+
188
+ // Legacy icon file support (no markers).
189
+ if (!this._availableStates.length) {
190
+ // Clone data before modifying.
191
+ this._iconData = deepClone(this._iconData);
192
+
193
+ // Extract customizable properties.
194
+ const properties = extractLottieProperties(this._iconData, { lottieInstance: false });
195
+
196
+ // Set initial state for legacy icons.
197
+ if (properties && this._initialProperties.state) {
198
+ const name = `state-${this._initialProperties.state.toLowerCase()}`;
199
+ updateLottieProperties(
200
+ this._iconData,
201
+ properties.filter(c => c.name.startsWith('state-')),
202
+ 0,
203
+ );
204
+ updateLottieProperties(
205
+ this._iconData,
206
+ properties.filter(c => c.name === name),
207
+ 1,
208
+ );
209
+ }
210
+
211
+ // Set initial stroke for legacy icons.
212
+ if (properties && this._initialProperties.stroke) {
213
+ const property = properties.filter(c => c.name === 'stroke')[0];
214
+ if (property) {
215
+ const ratio = property.value / 50;
216
+ const value = (this._initialProperties.stroke as number) * ratio;
217
+ set(this._iconData, property.path, value);
218
+ }
219
+ }
220
+
221
+ // Set initial scale for legacy icons.
222
+ if (properties && this._initialProperties.scale) {
223
+ const property = properties.filter(c => c.name === 'scale')[0];
224
+ if (property) {
225
+ const ratio = property.value / 50;
226
+ const value = (this._initialProperties.scale as number) * ratio;
227
+ set(this._iconData, property.path, value);
228
+ }
229
+ }
230
+
231
+ // Set initial axis for legacy icons.
232
+ if (properties && this._initialProperties.axisX && this._initialProperties.axisY) {
233
+ const property = properties.filter(c => c.name === 'axis')[0];
234
+ if (property) {
235
+ const ratio = ((property.value[0] + property.value[1]) / 2) / 50;
236
+ set(this._iconData, property.path + '.0', (this._initialProperties.axisX as number) * ratio);
237
+ set(this._iconData, property.path + '.1', (this._initialProperties.axisY as number) * ratio);
238
+ }
239
+ }
240
+ }
241
+
242
+ // Automatically initialize if requested.
243
+ if (options.autoInit) {
244
+ this.init();
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Initializes the player and connects it to the DOM element.
250
+ * Throws an error if already initialized.
251
+ */
252
+ init() {
253
+ if (this._lottieInstance) {
254
+ throw new Error('Already connected player!');
255
+ }
256
+
257
+ const fixedParams: any = {};
258
+ const initialOptions: LottieOptions = {};
259
+
260
+ // Set initial segment if state is defined.
261
+ if (this._state) {
262
+ initialOptions.initialSegment = [this._state.time, this._state.time + this._state.duration + 1];
263
+ }
264
+
265
+ // Adjust animation time for icons with states.
266
+ if (this._availableStates.length) {
267
+ const firstState = this._availableStates[0];
268
+ const lastState = this._availableStates[this._availableStates.length - 1];
269
+
270
+ fixedParams.ip = firstState.time;
271
+ fixedParams.op = lastState.time + lastState.duration + 1;
272
+ }
273
+
274
+ // Load the Lottie animation.
275
+ this._lottieInstance = lottie.loadAnimation({
276
+ ...DEFAULT_LOTTIE_WEB_OPTIONS,
277
+ ...initialOptions,
278
+ container: this._container,
279
+ animationData: Object.assign(deepClone(this._iconData), fixedParams),
280
+ });
281
+
282
+ // Set initial colors and stroke if provided.
283
+ if (this._initialProperties.colors) {
284
+ this.colors = this._initialProperties.colors;
285
+ }
286
+
287
+ if (this._initialProperties.stroke) {
288
+ this.stroke = this._initialProperties.stroke;
289
+ }
290
+
291
+ // Register event listeners for animation lifecycle.
292
+ this._lottieInstance.addEventListener('complete', () => {
293
+ this.triggerEvent('complete');
294
+ });
295
+
296
+ this._lottieInstance.addEventListener('loopComplete', () => {
297
+ this.triggerEvent('complete');
298
+ });
299
+
300
+ this._lottieInstance.addEventListener('enterFrame', () => {
301
+ this.triggerEvent('frame');
302
+ });
303
+
304
+ // Mark as ready when loaded.
305
+ if (this._lottieInstance.isLoaded) {
306
+ this._ready = true;
307
+ this.triggerEvent('ready');
308
+ } else {
309
+ this._lottieInstance.addEventListener('config_ready', () => {
310
+ this._ready = true;
311
+ this.triggerEvent('ready');
312
+ });
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Destroys the player and releases all resources.
318
+ * Throws an error if not initialized.
319
+ */
320
+ destroy() {
321
+ if (!this._lottieInstance) {
322
+ throw new Error('Not connected player!');
323
+ }
324
+
325
+ this._ready = false;
326
+
327
+ this._lottieInstance.destroy();
328
+ this._lottieInstance = undefined;
329
+
330
+ this._colorsProxy = undefined;
331
+ this._lottieProperties = undefined;
332
+ }
333
+
334
+ /**
335
+ * Registers an event listener for a player event.
336
+ * @param name Event name (e.g., 'complete', 'frame', 'ready').
337
+ * @param handler Handler function to call when the event is triggered.
338
+ * @returns Function to remove the listener.
339
+ */
340
+ addEventListener(
341
+ name: EventName,
342
+ handler: EventHandler,
343
+ ): () => void {
344
+ if (!this._eventHandlers[name]) {
345
+ this._eventHandlers[name] = [];
346
+ }
347
+ this._eventHandlers[name].push(handler);
348
+
349
+ return () => {
350
+ this.removeEventListener(name, handler);
351
+ };
352
+ }
353
+
354
+ /**
355
+ * Removes an event listener for a player event.
356
+ * @param name Event name.
357
+ * @param handler Handler function to remove. If not provided, removes all handlers for the event.
358
+ */
359
+ removeEventListener(
360
+ name: EventName,
361
+ handler?: EventHandler,
362
+ ) {
363
+ if (!handler) {
364
+ this._eventHandlers[name] = null;
365
+ } else if (this._eventHandlers[name]) {
366
+ let i = 0;
367
+ let len = this._eventHandlers[name].length;
368
+ while (i < len) {
369
+ if (this._eventHandlers[name][i] === handler) {
370
+ this._eventHandlers[name].splice(i, 1);
371
+ i -= 1;
372
+ len -= 1;
373
+ }
374
+ i += 1;
375
+ }
376
+ if (!this._eventHandlers[name].length) {
377
+ this._eventHandlers[name] = null;
378
+ }
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Triggers a player event and invokes all registered callbacks.
384
+ * @param name Event name.
385
+ * @param args Optional arguments to pass to the callbacks.
386
+ */
387
+ protected triggerEvent(
388
+ name: EventName,
389
+ args?: any,
390
+ ) {
391
+ if (this._eventHandlers[name]) {
392
+ const callbacks = this._eventHandlers[name];
393
+ for (let i = 0; i < callbacks.length; i += 1) {
394
+ callbacks[i](args);
395
+ }
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Forces a re-render of the animation.
401
+ */
402
+ protected refresh() {
403
+ if (!this._lottieInstance) throw new Error('Player not initialized');
404
+
405
+ this._lottieInstance.renderer.renderFrame(null);
406
+
407
+ this.triggerEvent('refresh');
408
+ }
409
+
410
+ /**
411
+ * Starts playing the animation from the current frame.
412
+ * Note: If the animation is finished, it cannot be played again from the last frame.
413
+ */
414
+ play() {
415
+ if (!this._lottieInstance) throw new Error('Player not initialized');
416
+
417
+ this._lottieInstance.setDirection(this._direction);
418
+ this._lottieInstance.play();
419
+ }
420
+
421
+ /**
422
+ * Plays the animation from the beginning of the current state or from the start if no state is set.
423
+ */
424
+ playFromStart() {
425
+ if (!this._lottieInstance) throw new Error('Player not initialized');
426
+
427
+ this._lottieInstance.setDirection(1);
428
+ if (this._state) {
429
+ this._lottieInstance.playSegments([this._state.time, this._state.time + this._state.duration + 1], true);
430
+ } else {
431
+ this._lottieInstance.goToAndPlay(0);
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Pauses the animation at the current frame.
437
+ */
438
+ pause() {
439
+ if (!this._lottieInstance) throw new Error('Player not initialized');
440
+
441
+ this._lottieInstance.pause();
442
+ }
443
+
444
+ /**
445
+ * Stop the animation.
446
+ */
447
+ stop() {
448
+ if (!this._lottieInstance) throw new Error('Player not initialized');
449
+
450
+ this._lottieInstance.stop();
451
+ }
452
+
453
+ /**
454
+ * Moves the animation to a specific frame and stops.
455
+ * @param frame Frame number to seek to.
456
+ */
457
+ seek(frame: number) {
458
+ if (!this._lottieInstance) throw new Error('Player not initialized');
459
+
460
+ this._lottieInstance.goToAndStop(frame, true);
461
+ }
462
+
463
+ /**
464
+ * Moves the animation to the first frame and stops.
465
+ */
466
+ seekToStart() {
467
+ this.seek(0);
468
+ }
469
+
470
+ /**
471
+ * Moves the animation to the last frame and stops.
472
+ */
473
+ seekToEnd() {
474
+ this.seek(Math.max(0, this.frameCount));
475
+ }
476
+
477
+ /**
478
+ * Sets the animation segment to play.
479
+ * If no segment is provided, resets to the default segment.
480
+ * @param segment Optional segment as [start, end] frame numbers.
481
+ */
482
+ switchSegment(segment?: [number, number]) {
483
+ if (!this._lottieInstance) throw new Error('Player not initialized');
484
+
485
+ if (segment) {
486
+ this._lottieInstance.setSegment(segment[0], segment[1]);
487
+ } else {
488
+ this._lottieInstance.resetSegments(true);
489
+ }
490
+
491
+ this._lottieInstance.goToAndStop(0, true);
492
+ }
493
+
494
+ /**
495
+ * Sets multiple icon properties at once.
496
+ * Any property not provided will be reset to its default value.
497
+ * @param properties Properties to assign.
498
+ */
499
+ set properties(properties: IconProperties) {
500
+ this.colors = properties.colors || null;
501
+ this.stroke = properties.stroke || null;
502
+ this.state = properties.state || null;
503
+ }
504
+
505
+ /**
506
+ * Gets the current icon properties (colors, stroke, state).
507
+ * @returns The current properties.
508
+ */
509
+ get properties(): IconProperties {
510
+ const result: IconProperties = {};
511
+
512
+ if (this.lottieProperties.filter(c => c.type === 'color').length) {
513
+ result.colors = { ...this.colors };
514
+ }
515
+
516
+ if (this.lottieProperties.filter(c => c.name === 'stroke' || c.name === 'stroke-layers').length) {
517
+ result.stroke = this.stroke!;
518
+ }
519
+
520
+ if (this._availableStates.length) {
521
+ result.state = this.state!;
522
+ }
523
+
524
+ return result;
525
+ }
526
+
527
+ /**
528
+ * Sets all customizable colors at once.
529
+ * Pass null to reset all colors to default.
530
+ * @param colors Color map or null.
531
+ */
532
+ set colors(colors: ColorMap | null) {
533
+ resetLottieProperties(
534
+ this._lottieInstance,
535
+ this.lottieProperties.filter(c => c.type === 'color'),
536
+ );
537
+
538
+ if (colors) {
539
+ for (const [key, value] of Object.entries(colors)) {
540
+ updateLottieProperties(
541
+ this._lottieInstance,
542
+ this.lottieProperties.filter(c => c.type === 'color' && c.name === key),
543
+ value,
544
+ );
545
+ }
546
+ }
547
+
548
+ this.refresh();
549
+ }
550
+
551
+ /**
552
+ * Provides a proxy for reading or updating individual colors by name.
553
+ *
554
+ * Example:
555
+ * player.colors.primary = '#ff0000';
556
+ * delete player.colors.secondary;
557
+ */
558
+ get colors(): ColorMap {
559
+ if (!this._colorsProxy) {
560
+ this._colorsProxy = createColorsProxy.call(this);
561
+ }
562
+
563
+ return this._colorsProxy;
564
+ }
565
+
566
+ /**
567
+ * Sets the stroke width for the icon.
568
+ * Pass null to reset to default.
569
+ * @param stroke Stroke value or null.
570
+ */
571
+ set stroke(stroke: Stroke | null) {
572
+ resetLottieProperties(
573
+ this._lottieInstance,
574
+ this.lottieProperties.filter(c => c.name === 'stroke' || c.name === 'stroke-layers'),
575
+ );
576
+
577
+ const newStroke = parseStroke(stroke!);
578
+
579
+ if (newStroke) {
580
+ updateLottieProperties(
581
+ this._lottieInstance,
582
+ this.lottieProperties.filter(c => c.name === 'stroke' || c.name === 'stroke-layers'),
583
+ newStroke,
584
+ );
585
+ }
586
+
587
+ this.refresh();
588
+ }
589
+
590
+ /**
591
+ * Gets the current stroke width of the icon.
592
+ * @returns Stroke value or null if not set.
593
+ */
594
+ get stroke(): Stroke | null {
595
+ const property = this.lottieProperties.filter(c => c.name === 'stroke' || c.name === 'stroke-layers')[0];
596
+
597
+ if (property) {
598
+ let value = +get(this._lottieInstance, property.path);
599
+
600
+ return parseStroke(value) || null;
601
+ }
602
+
603
+ return null;
604
+ }
605
+
606
+ /**
607
+ * Sets the current state (animation segment) of the icon.
608
+ * If the state does not exist, falls back to the default state.
609
+ * @param state State name or null for default.
610
+ */
611
+ set state(state: string | null) {
612
+ if (!this._lottieInstance) throw new Error('Player not initialized');
613
+ if (state === this.state) {
614
+ return;
615
+ }
616
+
617
+ const isPlaying = this.playing;
618
+
619
+ this._state = undefined;
620
+
621
+ if (isNil(state)) {
622
+ this._state = this._availableStates.filter(c => c.default)[0];
623
+ } else if (state) {
624
+ this._state = this._availableStates.filter(c => c.name === state)[0];
625
+
626
+ if (!this._state) {
627
+ this._state = this._availableStates.filter(c => c.default)[0];
628
+ }
629
+ }
630
+
631
+ this.switchSegment(
632
+ this._state ? [this._state.time, this._state.time + this._state.duration + 1] : undefined,
633
+ )
634
+
635
+ if (isPlaying) {
636
+ this.pause();
637
+ this.play();
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Gets the current state (animation segment) of the icon.
643
+ * @returns State name or null if not set.
644
+ */
645
+ get state(): string | null {
646
+ if (this._state) {
647
+ return this._state.name;
648
+ }
649
+
650
+ return '';
651
+ }
652
+
653
+ /**
654
+ * Sets the playback speed of the animation.
655
+ * @param speed Playback speed (1 = normal).
656
+ */
657
+ set speed(speed: number) {
658
+ this._speed = speed;
659
+ this._lottieInstance?.setSpeed(speed);
660
+ }
661
+
662
+ /**
663
+ * Gets the current playback speed.
664
+ * @returns Playback speed.
665
+ */
666
+ get speed() {
667
+ return this._speed;
668
+ }
669
+
670
+ /**
671
+ * Sets the playback direction.
672
+ * @param direction 1 for forward, -1 for reverse.
673
+ */
674
+ set direction(direction: PlaybackDirection) {
675
+ if (!this._lottieInstance) throw new Error('Player not initialized');
676
+
677
+ this._direction = direction;
678
+ this._lottieInstance.setDirection(direction);
679
+ }
680
+
681
+ /**
682
+ * Gets the current playback direction.
683
+ * @returns Playback direction.
684
+ */
685
+ get direction() {
686
+ return this._direction;
687
+ }
688
+
689
+ /**
690
+ * Enables or disables looping of the animation.
691
+ * @param loop True to loop, false otherwise.
692
+ */
693
+ set loop(loop: boolean) {
694
+ if (!this._lottieInstance) throw new Error('Player not initialized');
695
+
696
+ this._lottieInstance.loop = loop;
697
+ }
698
+
699
+ /**
700
+ * Gets whether the animation is set to loop.
701
+ * @returns True if looping, false otherwise.
702
+ */
703
+ get loop() {
704
+ if (!this._lottieInstance) throw new Error('Player not initialized');
705
+ return this._lottieInstance.loop ? true : false;
706
+ }
707
+
708
+ /**
709
+ * Sets the current frame of the animation.
710
+ * @param frame Frame number.
711
+ */
712
+ set frame(frame: number) {
713
+ this.seek(Math.max(0, Math.min(this.frameCount, frame)));
714
+ }
715
+
716
+ /**
717
+ * Gets the current frame of the animation.
718
+ * @returns Current frame number.
719
+ */
720
+ get frame() {
721
+ if (!this._lottieInstance) throw new Error('Player not initialized');
722
+
723
+ return this._lottieInstance.currentFrame;
724
+ }
725
+
726
+ /**
727
+ * Gets the list of available states for the icon.
728
+ * @returns Array of available states.
729
+ */
730
+ get availableStates() {
731
+ return this._availableStates;
732
+ }
733
+
734
+ /**
735
+ * Returns true if the animation is currently playing.
736
+ */
737
+ get playing() {
738
+ if (!this._lottieInstance) throw new Error('Player not initialized');
739
+
740
+ return !this._lottieInstance.isPaused;
741
+ }
742
+
743
+ /**
744
+ * Returns true if the player is ready for interaction.
745
+ */
746
+ get ready() {
747
+ return this._ready;
748
+ }
749
+
750
+ /**
751
+ * Gets the total number of frames in the animation.
752
+ * @returns Frame count.
753
+ */
754
+ get frameCount() {
755
+ if (!this._lottieInstance) throw new Error('Player not initialized');
756
+
757
+ return this._lottieInstance.getDuration(true) - 1;
758
+ }
759
+
760
+ /**
761
+ * Gets the current segment of the animation as [start, end] frame numbers.
762
+ * @returns Segment as [start, end].
763
+ */
764
+ get segment(): [number, number] {
765
+ if (!this._lottieInstance) throw new Error('Player not initialized');
766
+
767
+ return [
768
+ this._lottieInstance.firstFrame,
769
+ this._lottieInstance.firstFrame + this._lottieInstance.totalFrames,
770
+ ];
771
+ }
772
+
773
+ /**
774
+ * Gets the duration of the animation in seconds.
775
+ * @returns Duration in seconds.
776
+ */
777
+ get duration() {
778
+ if (!this._lottieInstance) throw new Error('Player not initialized');
779
+
780
+ return this._lottieInstance.getDuration(false);
781
+ }
782
+
783
+ /**
784
+ * Provides access to the underlying Lottie player instance.
785
+ * @returns LottieAnimationInstance.
786
+ */
787
+ get lottieInstance() {
788
+ return this._lottieInstance;
789
+ }
790
+
791
+ /**
792
+ * Gets all customizable properties for the icon.
793
+ * @returns Array of LottieProperty.
794
+ */
795
+ get lottieProperties(): LottieProperty[] {
796
+ if (!this._lottieProperties) {
797
+ this._lottieProperties = extractLottieProperties(this._iconData, { lottieInstance: true });
798
+
799
+ // legacy icon file support (without markers)
800
+ if (!this._availableStates.length && this._lottieProperties) {
801
+ this._lottieProperties = this._lottieProperties.filter(c => c.name !== 'scale' && c.name !== 'axis' && c.name !== 'stroke' && !c.name.startsWith('state-'));
802
+ }
803
+ }
804
+
805
+ return this._lottieProperties || [];
806
+ }
807
+ }