@eternalheart/angular-file-preview 1.6.5

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.
Files changed (119) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +879 -0
  3. package/README.zh-CN.md +876 -0
  4. package/lib/LICENSE +22 -0
  5. package/lib/README.md +879 -0
  6. package/lib/components/preview/file-preview-toolbar.d.ts +19 -0
  7. package/lib/components/preview/nav-arrows.d.ts +25 -0
  8. package/lib/components/preview/renderer-outlet.d.ts +23 -0
  9. package/lib/components/preview/toolbar-button.d.ts +12 -0
  10. package/lib/components/resizable-split.d.ts +32 -0
  11. package/lib/di/locale.service.d.ts +12 -0
  12. package/lib/di/request.service.d.ts +20 -0
  13. package/lib/di/theme-mode.d.ts +6 -0
  14. package/lib/di/theme.service.d.ts +9 -0
  15. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs +55 -0
  16. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs.map +1 -0
  17. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs +58 -0
  18. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs.map +1 -0
  19. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs +5835 -0
  20. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs.map +1 -0
  21. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs +17 -0
  22. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs.map +1 -0
  23. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs +284 -0
  24. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs.map +1 -0
  25. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs +703 -0
  26. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs.map +1 -0
  27. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs +231 -0
  28. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs.map +1 -0
  29. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs +552 -0
  30. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs.map +1 -0
  31. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs +688 -0
  32. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs.map +1 -0
  33. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs +273 -0
  34. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs.map +1 -0
  35. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs +288 -0
  36. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs.map +1 -0
  37. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs +550 -0
  38. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs.map +1 -0
  39. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs +102 -0
  40. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs.map +1 -0
  41. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs +516 -0
  42. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs.map +1 -0
  43. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs +823 -0
  44. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs.map +1 -0
  45. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs +164 -0
  46. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs.map +1 -0
  47. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs +655 -0
  48. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs.map +1 -0
  49. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs +207 -0
  50. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs.map +1 -0
  51. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs +199 -0
  52. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs.map +1 -0
  53. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs +194 -0
  54. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs.map +1 -0
  55. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs +94 -0
  56. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs.map +1 -0
  57. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs +468 -0
  58. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs.map +1 -0
  59. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs +193 -0
  60. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs.map +1 -0
  61. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs +184 -0
  62. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs.map +1 -0
  63. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs +492 -0
  64. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs.map +1 -0
  65. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs +68 -0
  66. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs.map +1 -0
  67. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs +36 -0
  68. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs.map +1 -0
  69. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs +121 -0
  70. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs.map +1 -0
  71. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs +78 -0
  72. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs.map +1 -0
  73. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs +81 -0
  74. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs.map +1 -0
  75. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs +59 -0
  76. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs.map +1 -0
  77. package/lib/fesm2022/eternalheart-angular-file-preview.mjs +2 -0
  78. package/lib/fesm2022/eternalheart-angular-file-preview.mjs.map +1 -0
  79. package/lib/file-preview-content.d.ts +65 -0
  80. package/lib/file-preview-embed.d.ts +30 -0
  81. package/lib/file-preview-modal.d.ts +34 -0
  82. package/lib/fp-core.d.ts +1 -0
  83. package/lib/index.css +2119 -0
  84. package/lib/index.d.ts +11 -0
  85. package/lib/renderers/Audio/index.d.ts +74 -0
  86. package/lib/renderers/Cad/index.d.ts +45 -0
  87. package/lib/renderers/Cad/toolbar.d.ts +15 -0
  88. package/lib/renderers/Csv/index.d.ts +26 -0
  89. package/lib/renderers/Doc/index.d.ts +19 -0
  90. package/lib/renderers/Docx/index.d.ts +42 -0
  91. package/lib/renderers/Epub/index.d.ts +57 -0
  92. package/lib/renderers/Font/font-preview-line.d.ts +26 -0
  93. package/lib/renderers/Font/index.d.ts +52 -0
  94. package/lib/renderers/Image/index.d.ts +79 -0
  95. package/lib/renderers/Json/index.d.ts +61 -0
  96. package/lib/renderers/Markdown/index.d.ts +35 -0
  97. package/lib/renderers/Mobi/index.d.ts +51 -0
  98. package/lib/renderers/Msg/index.d.ts +47 -0
  99. package/lib/renderers/Pdf/index.d.ts +55 -0
  100. package/lib/renderers/Ppt/index.d.ts +19 -0
  101. package/lib/renderers/Pptx/index.d.ts +29 -0
  102. package/lib/renderers/RendererError.d.ts +9 -0
  103. package/lib/renderers/RendererLoading.d.ts +7 -0
  104. package/lib/renderers/Subtitle/index.d.ts +25 -0
  105. package/lib/renderers/Text/index.d.ts +29 -0
  106. package/lib/renderers/Unsupported/index.d.ts +15 -0
  107. package/lib/renderers/Video/index.d.ts +28 -0
  108. package/lib/renderers/Xlsx/index.d.ts +25 -0
  109. package/lib/renderers/Xml/index.d.ts +26 -0
  110. package/lib/renderers/Zip/index.d.ts +58 -0
  111. package/lib/renderers/Zip/tree-item.d.ts +29 -0
  112. package/lib/renderers/base.types.d.ts +10 -0
  113. package/lib/renderers/lazy.d.ts +23 -0
  114. package/lib/renderers/registry.d.ts +13 -0
  115. package/lib/renderers/toolbar.types.d.ts +19 -0
  116. package/lib/types.d.ts +26 -0
  117. package/lib/utils/audio-player.d.ts +20 -0
  118. package/lib/utils/shiki-highlight.d.ts +7 -0
  119. package/package.json +119 -0
@@ -0,0 +1,823 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, inject, DestroyRef, signal, viewChild, computed, effect, untracked, afterNextRender, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import * as i1 from 'lucide-angular';
4
+ import { Play, Pause, SkipBack, SkipForward, Repeat, VolumeX, Volume1, Volume2, LucideAngularModule } from 'lucide-angular';
5
+ import { L as LocaleService, g as getFallbackTranslator, r as r0 } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs';
6
+ import { R as RendererError } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs';
7
+ import { T as ToolbarEventEmitter } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs';
8
+
9
+ function createAudioPlayer() {
10
+ const isPlaying = signal(false);
11
+ const isLoading = signal(true);
12
+ const currentTime = signal(0);
13
+ const duration = signal(0);
14
+ const volume = signal(1);
15
+ const isMuted = signal(false);
16
+ const isLoop = signal(false);
17
+ const error = signal(null);
18
+ let audio = null;
19
+ let unbind = null;
20
+ const bind = (el) => {
21
+ unbind?.();
22
+ unbind = null;
23
+ audio = el;
24
+ if (!el)
25
+ return;
26
+ isLoading.set(true);
27
+ error.set(null);
28
+ const onTimeUpdate = () => {
29
+ if (!isNaN(el.currentTime))
30
+ currentTime.set(el.currentTime);
31
+ };
32
+ const onDurationChange = () => {
33
+ if (!isNaN(el.duration) && isFinite(el.duration))
34
+ duration.set(el.duration);
35
+ };
36
+ const onCanPlay = () => {
37
+ isLoading.set(false);
38
+ onDurationChange();
39
+ };
40
+ const onWaiting = () => isLoading.set(true);
41
+ const onPlaying = () => {
42
+ isLoading.set(false);
43
+ isPlaying.set(true);
44
+ };
45
+ const onPlay = () => isPlaying.set(true);
46
+ const onPause = () => isPlaying.set(false);
47
+ const onEnded = () => isPlaying.set(false);
48
+ const onError = () => {
49
+ error.set('audio.load_failed');
50
+ isLoading.set(false);
51
+ };
52
+ el.addEventListener('timeupdate', onTimeUpdate);
53
+ el.addEventListener('loadedmetadata', onDurationChange);
54
+ el.addEventListener('durationchange', onDurationChange);
55
+ el.addEventListener('canplay', onCanPlay);
56
+ el.addEventListener('waiting', onWaiting);
57
+ el.addEventListener('playing', onPlaying);
58
+ el.addEventListener('play', onPlay);
59
+ el.addEventListener('pause', onPause);
60
+ el.addEventListener('ended', onEnded);
61
+ el.addEventListener('error', onError);
62
+ if (el.readyState >= 3) {
63
+ isLoading.set(false);
64
+ onDurationChange();
65
+ }
66
+ unbind = () => {
67
+ el.removeEventListener('timeupdate', onTimeUpdate);
68
+ el.removeEventListener('loadedmetadata', onDurationChange);
69
+ el.removeEventListener('durationchange', onDurationChange);
70
+ el.removeEventListener('canplay', onCanPlay);
71
+ el.removeEventListener('waiting', onWaiting);
72
+ el.removeEventListener('playing', onPlaying);
73
+ el.removeEventListener('play', onPlay);
74
+ el.removeEventListener('pause', onPause);
75
+ el.removeEventListener('ended', onEnded);
76
+ el.removeEventListener('error', onError);
77
+ };
78
+ };
79
+ const togglePlay = () => {
80
+ if (!audio)
81
+ return;
82
+ if (audio.paused)
83
+ void audio.play();
84
+ else
85
+ audio.pause();
86
+ };
87
+ const seek = (time) => {
88
+ if (!audio)
89
+ return;
90
+ audio.currentTime = time;
91
+ currentTime.set(time);
92
+ };
93
+ const skip = (seconds) => {
94
+ if (!audio)
95
+ return;
96
+ audio.currentTime = Math.max(0, Math.min(audio.currentTime + seconds, audio.duration || Infinity));
97
+ currentTime.set(audio.currentTime);
98
+ };
99
+ const setVolume = (v) => {
100
+ volume.set(v);
101
+ if (audio)
102
+ audio.volume = v;
103
+ if (v > 0 && isMuted()) {
104
+ isMuted.set(false);
105
+ if (audio)
106
+ audio.muted = false;
107
+ }
108
+ };
109
+ const toggleMute = () => {
110
+ const next = !isMuted();
111
+ isMuted.set(next);
112
+ if (audio)
113
+ audio.muted = next;
114
+ };
115
+ const toggleLoop = () => {
116
+ const next = !isLoop();
117
+ isLoop.set(next);
118
+ if (audio)
119
+ audio.loop = next;
120
+ };
121
+ const formattedCurrent = () => r0(currentTime());
122
+ const formattedDuration = () => r0(duration());
123
+ const destroy = () => {
124
+ unbind?.();
125
+ unbind = null;
126
+ audio = null;
127
+ };
128
+ return {
129
+ isPlaying,
130
+ isLoading,
131
+ currentTime,
132
+ duration,
133
+ volume,
134
+ isMuted,
135
+ isLoop,
136
+ error,
137
+ bind,
138
+ togglePlay,
139
+ seek,
140
+ skip,
141
+ setVolume,
142
+ toggleMute,
143
+ toggleLoop,
144
+ formattedCurrent,
145
+ formattedDuration,
146
+ destroy,
147
+ };
148
+ }
149
+
150
+ class AudioRenderer {
151
+ constructor() {
152
+ this.url = input.required();
153
+ this.fileName = input.required();
154
+ this.emitter = new ToolbarEventEmitter();
155
+ this.locale = inject(LocaleService, { optional: true });
156
+ this.destroyRef = inject(DestroyRef);
157
+ this.t = this.locale?.t() ?? getFallbackTranslator();
158
+ this.formatTime = r0;
159
+ this.Math = Math;
160
+ this.playIcon = Play;
161
+ this.pauseIcon = Pause;
162
+ this.skipBackIcon = SkipBack;
163
+ this.skipForwardIcon = SkipForward;
164
+ this.repeatIcon = Repeat;
165
+ this.player = createAudioPlayer();
166
+ this.isPlaying = this.player.isPlaying;
167
+ this.isLoop = this.player.isLoop;
168
+ this.currentTime = this.player.currentTime;
169
+ this.duration = this.player.duration;
170
+ this.volume = this.player.volume;
171
+ this.isMuted = this.player.isMuted;
172
+ this.error = this.player.error;
173
+ this.togglePlay = this.player.togglePlay;
174
+ this.toggleMute = this.player.toggleMute;
175
+ this.toggleLoop = this.player.toggleLoop;
176
+ this.showVolume = signal(false);
177
+ this.isCompact = signal(false);
178
+ this.controlScale = signal(1);
179
+ this.isDragging = signal(false);
180
+ this.dragTime = signal(0);
181
+ this.containerRef = viewChild('containerRef');
182
+ this.volumeRef = viewChild('volumeRef');
183
+ this.audioEl = viewChild('audioRef');
184
+ this.volumeHideTimer = null;
185
+ this.resizeObserver = null;
186
+ this.bindRaf = 0;
187
+ this.vinylBase = 260;
188
+ this.vinylHeightBase = 240;
189
+ this.displayTime = computed(() => this.isDragging() ? this.dragTime() : this.currentTime());
190
+ this.progress = computed(() => this.duration() > 0 ? this.displayTime() / this.duration() : 0);
191
+ this.vinylScale = computed(() => (this.isCompact() ? 0.72 : 1));
192
+ this.finalVinylScale = computed(() => this.vinylScale() * this.controlScale());
193
+ this.volumeIcon = computed(() => {
194
+ if (this.isMuted() || this.volume() === 0)
195
+ return VolumeX;
196
+ if (this.volume() < 0.5)
197
+ return Volume1;
198
+ return Volume2;
199
+ });
200
+ this.timeRowClass = computed(() => this.isCompact()
201
+ ? 'afp-flex afp-justify-between afp-text-fg-tertiary afp-text-[10px] afp-mt-1.5'
202
+ : 'afp-flex afp-justify-between afp-text-fg-tertiary afp-text-xs afp-mt-2.5');
203
+ this.getToolbarGroups = () => [];
204
+ this.onToolbarChange = (listener) => this.emitter.subscribe(listener);
205
+ this.checkSize = () => {
206
+ const el = this.containerRef()?.nativeElement;
207
+ if (!el)
208
+ return;
209
+ this.isCompact.set(el.clientHeight < 580);
210
+ const width = el.clientWidth;
211
+ const baseWidth = 464;
212
+ const minVisualWidth = 320;
213
+ const minScale = minVisualWidth / baseWidth;
214
+ const scale = width >= baseWidth ? 1 : Math.max(minScale, width / baseWidth);
215
+ this.controlScale.set(scale);
216
+ };
217
+ this.handleClickOutside = (e) => {
218
+ const el = this.volumeRef()?.nativeElement;
219
+ if (el && !el.contains(e.target)) {
220
+ this.showVolume.set(false);
221
+ }
222
+ };
223
+ effect(() => {
224
+ this.url();
225
+ const el = this.audioEl()?.nativeElement ?? null;
226
+ untracked(() => {
227
+ cancelAnimationFrame(this.bindRaf);
228
+ this.bindRaf = requestAnimationFrame(() => {
229
+ this.player.bind(el);
230
+ });
231
+ });
232
+ });
233
+ afterNextRender(() => {
234
+ this.checkSize();
235
+ const el = this.containerRef()?.nativeElement;
236
+ if (el) {
237
+ this.resizeObserver = new ResizeObserver(() => this.checkSize());
238
+ this.resizeObserver.observe(el);
239
+ }
240
+ document.addEventListener('mousedown', this.handleClickOutside);
241
+ });
242
+ this.destroyRef.onDestroy(() => {
243
+ document.removeEventListener('mousedown', this.handleClickOutside);
244
+ if (this.volumeHideTimer !== null)
245
+ clearTimeout(this.volumeHideTimer);
246
+ cancelAnimationFrame(this.bindRaf);
247
+ this.resizeObserver?.disconnect();
248
+ this.resizeObserver = null;
249
+ this.player.destroy();
250
+ });
251
+ }
252
+ skip(seconds) {
253
+ const dur = this.duration();
254
+ const next = Math.max(0, Math.min(this.currentTime() + seconds, dur || Infinity));
255
+ this.player.seek(next);
256
+ }
257
+ onProgressPointerDown() {
258
+ this.dragTime.set(this.currentTime());
259
+ this.isDragging.set(true);
260
+ }
261
+ onProgressInput(e) {
262
+ const value = parseFloat(e.target.value);
263
+ if (this.isDragging()) {
264
+ this.dragTime.set(value);
265
+ }
266
+ else {
267
+ this.player.seek(value);
268
+ }
269
+ }
270
+ onProgressPointerUp(e) {
271
+ const value = parseFloat(e.target.value);
272
+ this.player.seek(value);
273
+ this.isDragging.set(false);
274
+ }
275
+ onVolumeInput(e) {
276
+ this.player.setVolume(parseFloat(e.target.value));
277
+ }
278
+ handleVolumeEnter() {
279
+ if (this.volumeHideTimer !== null)
280
+ clearTimeout(this.volumeHideTimer);
281
+ this.showVolume.set(true);
282
+ }
283
+ handleVolumeLeave() {
284
+ this.volumeHideTimer = window.setTimeout(() => {
285
+ this.showVolume.set(false);
286
+ }, 300);
287
+ }
288
+ vinylBoxStyle() {
289
+ const scale = this.finalVinylScale();
290
+ const compact = this.isCompact();
291
+ const m = compact ? `${-(this.vinylHeightBase * (1 - scale)) / 2}px` : '0';
292
+ return `width:${this.vinylBase}px;height:${this.vinylHeightBase}px;transform:scale(${scale});transform-origin:center center;margin-top:${m};margin-bottom:${m}`;
293
+ }
294
+ glowStyle() {
295
+ return `width:220px;height:220px;top:18px;left:8px;background:radial-gradient(circle, rgba(234,5,96,0.16) 0%, rgba(136,34,255,0.08) 42%, transparent 70%);opacity:${this.isPlaying() ? 0.7 : 0.2};transition:opacity 0.5s`;
296
+ }
297
+ vinylStyle() {
298
+ const playing = this.isPlaying();
299
+ const shadow = playing
300
+ ? '0 0 36px rgba(241,7,18,0.18), 0 0 20px rgba(136,34,255,0.14), 0 8px 32px rgba(0,0,0,0.4), inset 0 0 20px rgba(0,0,0,0.4)'
301
+ : '0 8px 32px rgba(0,0,0,0.4), inset 0 0 20px rgba(0,0,0,0.4)';
302
+ return `width:200px;height:200px;top:28px;left:18px;background:radial-gradient(circle at center, transparent 95%, rgba(30,30,30,0.8) 95.5%, #111 97%),radial-gradient(circle at center, transparent 38%, rgba(50,50,50,0.5) 38.15%, transparent 38.4%),radial-gradient(circle at center, transparent 45%, rgba(50,50,50,0.3) 45.15%, transparent 45.4%),radial-gradient(circle at center, transparent 52%, rgba(50,50,50,0.5) 52.15%, transparent 52.4%),radial-gradient(circle at center, transparent 59%, rgba(50,50,50,0.3) 59.15%, transparent 59.4%),radial-gradient(circle at center, transparent 66%, rgba(50,50,50,0.5) 66.15%, transparent 66.4%),radial-gradient(circle at center, transparent 73%, rgba(50,50,50,0.3) 73.15%, transparent 73.4%),radial-gradient(circle at center, transparent 80%, rgba(50,50,50,0.4) 80.15%, transparent 80.4%),radial-gradient(circle at center, transparent 87%, rgba(50,50,50,0.3) 87.15%, transparent 87.4%),conic-gradient(from 0deg, #1c1c1c, #232323, #1a1a1a, #262626, #1c1c1c, #212121, #1a1a1a, #252525, #1c1c1c, #232323, #1a1a1a, #262626, #1c1c1c);box-shadow:${shadow};animation:afp-vinyl-spin 8s linear infinite;animation-play-state:${playing ? 'running' : 'paused'}`;
303
+ }
304
+ labelStyle() {
305
+ return 'width:34%;height:34%;top:33%;left:33%;background:radial-gradient(circle at 40% 38%, #FA31A1, #F10712, #EA0560, #8822FF);box-shadow:inset 0 1px 3px rgba(255,255,255,0.25), inset 0 -1px 3px rgba(0,0,0,0.3), 0 0 8px rgba(0,0,0,0.3)';
306
+ }
307
+ spindleStyle() {
308
+ return 'width:14%;height:14%;top:43%;left:43%;background:radial-gradient(circle at 40% 40%, #333, #0d0d0d);box-shadow:inset 0 1px 3px rgba(0,0,0,0.9), 0 0 2px rgba(0,0,0,0.5)';
309
+ }
310
+ tonearmStyle() {
311
+ return `top:-6px;right:2px;width:100px;height:120px;transform-origin:76px 16px;z-index:5;transform:rotate(${this.isPlaying() ? 16 : 0}deg);transition:transform 0.8s cubic-bezier(0.4, 0, 0.2, 1)`;
312
+ }
313
+ panelStyle() {
314
+ const scale = this.controlScale();
315
+ const transform = scale < 1 ? `scale(${scale})` : undefined;
316
+ const marginBottom = scale < 1 ? `${-(1 - scale) * 100}px` : undefined;
317
+ return `width:448px;backdrop-filter:blur(16px)${transform ? `;transform:${transform};transform-origin:top center` : ''}${marginBottom ? `;margin-bottom:${marginBottom}` : ''}`;
318
+ }
319
+ progressFillStyle() {
320
+ return `width:${this.progress() * 100}%;background:var(--fp-accent-gradient);box-shadow:${this.isPlaying() ? '0 0 8px rgba(136,34,255,0.4)' : 'none'};transition:${this.isDragging() ? 'none' : 'width 0.1s linear'}`;
321
+ }
322
+ playBtnStyle() {
323
+ return 'background:var(--fp-accent-gradient);color:var(--fp-fg-inverse);box-shadow:0 4px 20px rgba(136,34,255,0.35)';
324
+ }
325
+ volumePopupStyle() {
326
+ return 'left:50%;margin-left:-27px;backdrop-filter:blur(16px)';
327
+ }
328
+ volumeFillStyle() {
329
+ const h = (this.isMuted() ? 0 : this.volume()) * 100;
330
+ return `width:3px;height:${h}%;background:var(--fp-accent-hover);transition:height 0.1s linear`;
331
+ }
332
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AudioRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
333
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: AudioRenderer, isStandalone: true, selector: "afp-audio-renderer", inputs: { url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: true, transformFunction: null }, fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "afp-block afp-w-full afp-h-full" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["containerRef"], descendants: true, isSignal: true }, { propertyName: "volumeRef", first: true, predicate: ["volumeRef"], descendants: true, isSignal: true }, { propertyName: "audioEl", first: true, predicate: ["audioRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
334
+ @if (error(); as err) {
335
+ <afp-renderer-error [message]="t(err)" />
336
+ } @else {
337
+ <div
338
+ #containerRef
339
+ class="afp-flex afp-flex-col afp-items-center afp-justify-center afp-w-full afp-h-full afp-select-none afp-overflow-auto"
340
+ [class.afp-p-3]="isCompact()"
341
+ [class.afp-gap-3]="isCompact()"
342
+ [class.afp-p-6]="!isCompact()"
343
+ [class.afp-gap-6]="!isCompact()"
344
+ >
345
+ <div class="afp-relative afp-flex-shrink-0" [attr.style]="vinylBoxStyle()">
346
+ <div
347
+ class="afp-absolute afp-rounded-full"
348
+ [attr.style]="glowStyle()"
349
+ ></div>
350
+
351
+ <div
352
+ class="afp-absolute afp-rounded-full afp-overflow-hidden"
353
+ [attr.style]="vinylStyle()"
354
+ >
355
+ <div class="afp-absolute afp-rounded-full" [attr.style]="labelStyle()">
356
+ <div class="afp-absolute afp-rounded-full" [attr.style]="spindleStyle()"></div>
357
+ </div>
358
+ </div>
359
+
360
+ <div class="afp-absolute" [attr.style]="tonearmStyle()">
361
+ <svg width="100" height="120" viewBox="0 0 100 120" fill="none" xmlns="http://www.w3.org/2000/svg">
362
+ <circle cx="76" cy="16" r="13" fill="rgba(0,0,0,0.3)" />
363
+ <circle cx="76" cy="16" r="11" fill="url(#afp-baseGrad)" />
364
+ <circle cx="76" cy="16" r="6" fill="url(#afp-baseInnerGrad)" />
365
+ <circle cx="76" cy="16" r="2.5" fill="#222" stroke="#555" stroke-width="0.5" />
366
+ <path d="M74 22 L56 88" stroke="url(#afp-armGrad)" stroke-width="3.5" stroke-linecap="round" />
367
+ <rect x="50" y="86" width="12" height="7" rx="1.5" fill="url(#afp-headGrad)" />
368
+ <rect x="52.5" y="92" width="7" height="9" rx="1" fill="url(#afp-cartridgeGrad)" />
369
+ <line x1="56" y1="101" x2="56" y2="105" stroke="#bbb" stroke-width="1.2" stroke-linecap="round" />
370
+ <circle cx="56" cy="105.5" r="0.8" fill="#ddd" />
371
+
372
+ <defs>
373
+ <radialGradient id="afp-baseGrad" cx="40%" cy="35%">
374
+ <stop offset="0%" stop-color="#555" />
375
+ <stop offset="100%" stop-color="#1a1a1a" />
376
+ </radialGradient>
377
+ <radialGradient id="afp-baseInnerGrad" cx="40%" cy="35%">
378
+ <stop offset="0%" stop-color="#666" />
379
+ <stop offset="100%" stop-color="#333" />
380
+ </radialGradient>
381
+ <linearGradient id="afp-armGrad" x1="0" y1="0" x2="0" y2="1">
382
+ <stop offset="0%" stop-color="#555" />
383
+ <stop offset="50%" stop-color="#444" />
384
+ <stop offset="100%" stop-color="#333" />
385
+ </linearGradient>
386
+ <linearGradient id="afp-headGrad" x1="0" y1="0" x2="0" y2="1">
387
+ <stop offset="0%" stop-color="#555" />
388
+ <stop offset="100%" stop-color="#333" />
389
+ </linearGradient>
390
+ <linearGradient id="afp-cartridgeGrad" x1="0" y1="0" x2="0" y2="1">
391
+ <stop offset="0%" stop-color="#444" />
392
+ <stop offset="100%" stop-color="#222" />
393
+ </linearGradient>
394
+ </defs>
395
+ </svg>
396
+ </div>
397
+ </div>
398
+
399
+ <div
400
+ class="afp-text-center afp-max-w-md afp-flex-shrink-0"
401
+ [class.afp-px-2]="isCompact()"
402
+ [class.afp-px-4]="!isCompact()"
403
+ >
404
+ <div
405
+ class="afp-font-medium afp-truncate afp-text-fg-primary"
406
+ [class.afp-text-sm]="isCompact()"
407
+ [class.afp-text-lg]="!isCompact()"
408
+ >
409
+ {{ fileName() }}
410
+ </div>
411
+ </div>
412
+
413
+ <div class="afp-w-full afp-flex afp-justify-center afp-flex-shrink-0">
414
+ <div
415
+ class="afp-rounded-2xl afp-border afp-bg-surface-1 afp-border-line-weak"
416
+ [class.afp-p-3]="isCompact()"
417
+ [class.afp-p-5]="!isCompact()"
418
+ [attr.style]="panelStyle()"
419
+ >
420
+ <div [class.afp-mb-3]="isCompact()" [class.afp-mb-5]="!isCompact()">
421
+ <div class="afp-relative afp-h-4 afp-flex afp-items-center">
422
+ <div class="afp-absolute afp-w-full afp-h-[5px] afp-rounded-full afp-bg-surface-2"></div>
423
+ <div class="afp-absolute afp-h-[5px] afp-rounded-full afp-pointer-events-none" [attr.style]="progressFillStyle()"></div>
424
+ <input
425
+ type="range"
426
+ min="0"
427
+ [max]="duration() > 0 ? duration() : currentTime() || 100"
428
+ step="any"
429
+ [value]="displayTime()"
430
+ [disabled]="duration() <= 0"
431
+ [attr.aria-label]="t('audio.aria.progress')"
432
+ class="audio-slider afp-absolute afp-w-full"
433
+ (pointerdown)="onProgressPointerDown()"
434
+ (input)="onProgressInput($event)"
435
+ (pointerup)="onProgressPointerUp($event)"
436
+ (pointercancel)="isDragging.set(false)"
437
+ />
438
+ </div>
439
+ <div [class]="timeRowClass()">
440
+ <span style="font-variant-numeric: tabular-nums">{{ formatTime(displayTime()) }}</span>
441
+ <span style="font-variant-numeric: tabular-nums">{{ duration() > 0 ? formatTime(duration()) : '--:--' }}</span>
442
+ </div>
443
+ </div>
444
+
445
+ <div
446
+ class="afp-flex afp-items-center afp-justify-center"
447
+ [class.afp-gap-2]="isCompact()"
448
+ [class.afp-gap-3]="!isCompact()"
449
+ >
450
+ <button
451
+ type="button"
452
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-flex-shrink-0"
453
+ [class.afp-w-8]="isCompact()"
454
+ [class.afp-h-8]="isCompact()"
455
+ [class.afp-w-9]="!isCompact()"
456
+ [class.afp-h-9]="!isCompact()"
457
+ [class.afp-bg-accent-soft]="isLoop()"
458
+ [class.afp-text-accent]="isLoop()"
459
+ [class.afp-bg-surface-2]="!isLoop()"
460
+ [class.afp-text-fg-tertiary]="!isLoop()"
461
+ [attr.aria-label]="isLoop() ? t('audio.aria.loop_off') : t('audio.aria.loop_on')"
462
+ (click)="toggleLoop()"
463
+ >
464
+ <i-lucide [img]="repeatIcon" [class]="isCompact() ? 'afp-w-3.5 afp-h-3.5' : 'afp-w-4 afp-h-4'" />
465
+ </button>
466
+
467
+ <button
468
+ type="button"
469
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-bg-surface-2 afp-text-fg-secondary afp-flex-shrink-0"
470
+ [class.afp-w-9]="isCompact()"
471
+ [class.afp-h-9]="isCompact()"
472
+ [class.afp-w-10]="!isCompact()"
473
+ [class.afp-h-10]="!isCompact()"
474
+ [attr.aria-label]="t('audio.aria.backward_10')"
475
+ (click)="skip(-10)"
476
+ >
477
+ <i-lucide [img]="skipBackIcon" [class]="isCompact() ? 'afp-w-4 afp-h-4' : 'afp-w-[18px] afp-h-[18px]'" />
478
+ </button>
479
+
480
+ <button
481
+ type="button"
482
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center audio-ctrl-btn afp-flex-shrink-0"
483
+ [class.afp-w-12]="isCompact()"
484
+ [class.afp-h-12]="isCompact()"
485
+ [class.afp-w-14]="!isCompact()"
486
+ [class.afp-h-14]="!isCompact()"
487
+ [attr.style]="playBtnStyle()"
488
+ [attr.aria-label]="isPlaying() ? t('audio.aria.pause') : t('audio.aria.play')"
489
+ (click)="togglePlay()"
490
+ >
491
+ @if (isPlaying()) {
492
+ <i-lucide [img]="pauseIcon" [class]="isCompact() ? 'afp-w-5 afp-h-5' : 'afp-w-6 afp-h-6'" />
493
+ } @else {
494
+ <i-lucide [img]="playIcon" [class]="isCompact() ? 'afp-w-5 afp-h-5 afp-ml-0.5' : 'afp-w-6 afp-h-6 afp-ml-0.5'" />
495
+ }
496
+ </button>
497
+
498
+ <button
499
+ type="button"
500
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-bg-surface-2 afp-text-fg-secondary afp-flex-shrink-0"
501
+ [class.afp-w-9]="isCompact()"
502
+ [class.afp-h-9]="isCompact()"
503
+ [class.afp-w-10]="!isCompact()"
504
+ [class.afp-h-10]="!isCompact()"
505
+ [attr.aria-label]="t('audio.aria.forward_10')"
506
+ (click)="skip(10)"
507
+ >
508
+ <i-lucide [img]="skipForwardIcon" [class]="isCompact() ? 'afp-w-4 afp-h-4' : 'afp-w-[18px] afp-h-[18px]'" />
509
+ </button>
510
+
511
+ <div
512
+ #volumeRef
513
+ class="afp-relative"
514
+ (mouseenter)="handleVolumeEnter()"
515
+ (mouseleave)="handleVolumeLeave()"
516
+ >
517
+ <button
518
+ type="button"
519
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-flex-shrink-0"
520
+ [class.afp-w-8]="isCompact()"
521
+ [class.afp-h-8]="isCompact()"
522
+ [class.afp-w-9]="!isCompact()"
523
+ [class.afp-h-9]="!isCompact()"
524
+ [class.afp-bg-accent-soft]="showVolume()"
525
+ [class.afp-text-accent]="showVolume()"
526
+ [class.afp-bg-surface-2]="!showVolume()"
527
+ [class.afp-text-fg-secondary]="!showVolume()"
528
+ [attr.aria-label]="isMuted() ? t('audio.aria.unmute') : t('audio.aria.mute')"
529
+ (click)="toggleMute()"
530
+ >
531
+ <i-lucide [img]="volumeIcon()" [class]="isCompact() ? 'afp-w-3.5 afp-h-3.5' : 'afp-w-4 afp-h-4'" />
532
+ </button>
533
+
534
+ @if (showVolume()) {
535
+ <div
536
+ class="afp-absolute afp-bottom-full afp-mb-2 afp-rounded-xl afp-p-3 afp-border afp-bg-surface-3 afp-border-line afp-volume-popup"
537
+ [attr.style]="volumePopupStyle()"
538
+ (mouseenter)="handleVolumeEnter()"
539
+ (mouseleave)="handleVolumeLeave()"
540
+ >
541
+ <div class="afp-flex afp-flex-col afp-items-center afp-gap-2" style="height: 100px">
542
+ <div class="afp-relative afp-flex afp-items-center afp-justify-center" style="width: 24px; height: 80px">
543
+ <div class="afp-absolute afp-rounded-full afp-bg-surface-2" style="width: 3px; height: 100%"></div>
544
+ <div
545
+ class="afp-absolute afp-bottom-0 afp-rounded-full afp-pointer-events-none"
546
+ [attr.style]="volumeFillStyle()"
547
+ ></div>
548
+ <input
549
+ type="range"
550
+ min="0"
551
+ max="1"
552
+ step="0.01"
553
+ [value]="isMuted() ? 0 : volume()"
554
+ [attr.aria-label]="t('audio.aria.volume')"
555
+ class="volume-slider-vertical afp-absolute"
556
+ style="width: 80px; height: 24px; transform: rotate(-90deg); transform-origin: center center"
557
+ (input)="onVolumeInput($event)"
558
+ />
559
+ </div>
560
+ <span class="afp-text-[10px] afp-tabular-nums afp-text-fg-tertiary">
561
+ {{ Math.round((isMuted() ? 0 : volume()) * 100) }}
562
+ </span>
563
+ </div>
564
+ </div>
565
+ }
566
+ </div>
567
+ </div>
568
+ </div>
569
+ </div>
570
+
571
+ <audio #audioRef [src]="url()" class="afp-hidden"></audio>
572
+ </div>
573
+ }
574
+ `, isInline: true, styles: [".audio-ctrl-btn{border:0;cursor:pointer}.afp-volume-popup{animation:afp-fade-in .2s ease}@keyframes afp-fade-in{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }, { kind: "component", type: RendererError, selector: "afp-renderer-error", inputs: ["message", "detail", "showIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
575
+ }
576
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: AudioRenderer, decorators: [{
577
+ type: Component,
578
+ args: [{ selector: 'afp-audio-renderer', standalone: true, imports: [LucideAngularModule, RendererError], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'afp-block afp-w-full afp-h-full' }, template: `
579
+ @if (error(); as err) {
580
+ <afp-renderer-error [message]="t(err)" />
581
+ } @else {
582
+ <div
583
+ #containerRef
584
+ class="afp-flex afp-flex-col afp-items-center afp-justify-center afp-w-full afp-h-full afp-select-none afp-overflow-auto"
585
+ [class.afp-p-3]="isCompact()"
586
+ [class.afp-gap-3]="isCompact()"
587
+ [class.afp-p-6]="!isCompact()"
588
+ [class.afp-gap-6]="!isCompact()"
589
+ >
590
+ <div class="afp-relative afp-flex-shrink-0" [attr.style]="vinylBoxStyle()">
591
+ <div
592
+ class="afp-absolute afp-rounded-full"
593
+ [attr.style]="glowStyle()"
594
+ ></div>
595
+
596
+ <div
597
+ class="afp-absolute afp-rounded-full afp-overflow-hidden"
598
+ [attr.style]="vinylStyle()"
599
+ >
600
+ <div class="afp-absolute afp-rounded-full" [attr.style]="labelStyle()">
601
+ <div class="afp-absolute afp-rounded-full" [attr.style]="spindleStyle()"></div>
602
+ </div>
603
+ </div>
604
+
605
+ <div class="afp-absolute" [attr.style]="tonearmStyle()">
606
+ <svg width="100" height="120" viewBox="0 0 100 120" fill="none" xmlns="http://www.w3.org/2000/svg">
607
+ <circle cx="76" cy="16" r="13" fill="rgba(0,0,0,0.3)" />
608
+ <circle cx="76" cy="16" r="11" fill="url(#afp-baseGrad)" />
609
+ <circle cx="76" cy="16" r="6" fill="url(#afp-baseInnerGrad)" />
610
+ <circle cx="76" cy="16" r="2.5" fill="#222" stroke="#555" stroke-width="0.5" />
611
+ <path d="M74 22 L56 88" stroke="url(#afp-armGrad)" stroke-width="3.5" stroke-linecap="round" />
612
+ <rect x="50" y="86" width="12" height="7" rx="1.5" fill="url(#afp-headGrad)" />
613
+ <rect x="52.5" y="92" width="7" height="9" rx="1" fill="url(#afp-cartridgeGrad)" />
614
+ <line x1="56" y1="101" x2="56" y2="105" stroke="#bbb" stroke-width="1.2" stroke-linecap="round" />
615
+ <circle cx="56" cy="105.5" r="0.8" fill="#ddd" />
616
+
617
+ <defs>
618
+ <radialGradient id="afp-baseGrad" cx="40%" cy="35%">
619
+ <stop offset="0%" stop-color="#555" />
620
+ <stop offset="100%" stop-color="#1a1a1a" />
621
+ </radialGradient>
622
+ <radialGradient id="afp-baseInnerGrad" cx="40%" cy="35%">
623
+ <stop offset="0%" stop-color="#666" />
624
+ <stop offset="100%" stop-color="#333" />
625
+ </radialGradient>
626
+ <linearGradient id="afp-armGrad" x1="0" y1="0" x2="0" y2="1">
627
+ <stop offset="0%" stop-color="#555" />
628
+ <stop offset="50%" stop-color="#444" />
629
+ <stop offset="100%" stop-color="#333" />
630
+ </linearGradient>
631
+ <linearGradient id="afp-headGrad" x1="0" y1="0" x2="0" y2="1">
632
+ <stop offset="0%" stop-color="#555" />
633
+ <stop offset="100%" stop-color="#333" />
634
+ </linearGradient>
635
+ <linearGradient id="afp-cartridgeGrad" x1="0" y1="0" x2="0" y2="1">
636
+ <stop offset="0%" stop-color="#444" />
637
+ <stop offset="100%" stop-color="#222" />
638
+ </linearGradient>
639
+ </defs>
640
+ </svg>
641
+ </div>
642
+ </div>
643
+
644
+ <div
645
+ class="afp-text-center afp-max-w-md afp-flex-shrink-0"
646
+ [class.afp-px-2]="isCompact()"
647
+ [class.afp-px-4]="!isCompact()"
648
+ >
649
+ <div
650
+ class="afp-font-medium afp-truncate afp-text-fg-primary"
651
+ [class.afp-text-sm]="isCompact()"
652
+ [class.afp-text-lg]="!isCompact()"
653
+ >
654
+ {{ fileName() }}
655
+ </div>
656
+ </div>
657
+
658
+ <div class="afp-w-full afp-flex afp-justify-center afp-flex-shrink-0">
659
+ <div
660
+ class="afp-rounded-2xl afp-border afp-bg-surface-1 afp-border-line-weak"
661
+ [class.afp-p-3]="isCompact()"
662
+ [class.afp-p-5]="!isCompact()"
663
+ [attr.style]="panelStyle()"
664
+ >
665
+ <div [class.afp-mb-3]="isCompact()" [class.afp-mb-5]="!isCompact()">
666
+ <div class="afp-relative afp-h-4 afp-flex afp-items-center">
667
+ <div class="afp-absolute afp-w-full afp-h-[5px] afp-rounded-full afp-bg-surface-2"></div>
668
+ <div class="afp-absolute afp-h-[5px] afp-rounded-full afp-pointer-events-none" [attr.style]="progressFillStyle()"></div>
669
+ <input
670
+ type="range"
671
+ min="0"
672
+ [max]="duration() > 0 ? duration() : currentTime() || 100"
673
+ step="any"
674
+ [value]="displayTime()"
675
+ [disabled]="duration() <= 0"
676
+ [attr.aria-label]="t('audio.aria.progress')"
677
+ class="audio-slider afp-absolute afp-w-full"
678
+ (pointerdown)="onProgressPointerDown()"
679
+ (input)="onProgressInput($event)"
680
+ (pointerup)="onProgressPointerUp($event)"
681
+ (pointercancel)="isDragging.set(false)"
682
+ />
683
+ </div>
684
+ <div [class]="timeRowClass()">
685
+ <span style="font-variant-numeric: tabular-nums">{{ formatTime(displayTime()) }}</span>
686
+ <span style="font-variant-numeric: tabular-nums">{{ duration() > 0 ? formatTime(duration()) : '--:--' }}</span>
687
+ </div>
688
+ </div>
689
+
690
+ <div
691
+ class="afp-flex afp-items-center afp-justify-center"
692
+ [class.afp-gap-2]="isCompact()"
693
+ [class.afp-gap-3]="!isCompact()"
694
+ >
695
+ <button
696
+ type="button"
697
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-flex-shrink-0"
698
+ [class.afp-w-8]="isCompact()"
699
+ [class.afp-h-8]="isCompact()"
700
+ [class.afp-w-9]="!isCompact()"
701
+ [class.afp-h-9]="!isCompact()"
702
+ [class.afp-bg-accent-soft]="isLoop()"
703
+ [class.afp-text-accent]="isLoop()"
704
+ [class.afp-bg-surface-2]="!isLoop()"
705
+ [class.afp-text-fg-tertiary]="!isLoop()"
706
+ [attr.aria-label]="isLoop() ? t('audio.aria.loop_off') : t('audio.aria.loop_on')"
707
+ (click)="toggleLoop()"
708
+ >
709
+ <i-lucide [img]="repeatIcon" [class]="isCompact() ? 'afp-w-3.5 afp-h-3.5' : 'afp-w-4 afp-h-4'" />
710
+ </button>
711
+
712
+ <button
713
+ type="button"
714
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-bg-surface-2 afp-text-fg-secondary afp-flex-shrink-0"
715
+ [class.afp-w-9]="isCompact()"
716
+ [class.afp-h-9]="isCompact()"
717
+ [class.afp-w-10]="!isCompact()"
718
+ [class.afp-h-10]="!isCompact()"
719
+ [attr.aria-label]="t('audio.aria.backward_10')"
720
+ (click)="skip(-10)"
721
+ >
722
+ <i-lucide [img]="skipBackIcon" [class]="isCompact() ? 'afp-w-4 afp-h-4' : 'afp-w-[18px] afp-h-[18px]'" />
723
+ </button>
724
+
725
+ <button
726
+ type="button"
727
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center audio-ctrl-btn afp-flex-shrink-0"
728
+ [class.afp-w-12]="isCompact()"
729
+ [class.afp-h-12]="isCompact()"
730
+ [class.afp-w-14]="!isCompact()"
731
+ [class.afp-h-14]="!isCompact()"
732
+ [attr.style]="playBtnStyle()"
733
+ [attr.aria-label]="isPlaying() ? t('audio.aria.pause') : t('audio.aria.play')"
734
+ (click)="togglePlay()"
735
+ >
736
+ @if (isPlaying()) {
737
+ <i-lucide [img]="pauseIcon" [class]="isCompact() ? 'afp-w-5 afp-h-5' : 'afp-w-6 afp-h-6'" />
738
+ } @else {
739
+ <i-lucide [img]="playIcon" [class]="isCompact() ? 'afp-w-5 afp-h-5 afp-ml-0.5' : 'afp-w-6 afp-h-6 afp-ml-0.5'" />
740
+ }
741
+ </button>
742
+
743
+ <button
744
+ type="button"
745
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-bg-surface-2 afp-text-fg-secondary afp-flex-shrink-0"
746
+ [class.afp-w-9]="isCompact()"
747
+ [class.afp-h-9]="isCompact()"
748
+ [class.afp-w-10]="!isCompact()"
749
+ [class.afp-h-10]="!isCompact()"
750
+ [attr.aria-label]="t('audio.aria.forward_10')"
751
+ (click)="skip(10)"
752
+ >
753
+ <i-lucide [img]="skipForwardIcon" [class]="isCompact() ? 'afp-w-4 afp-h-4' : 'afp-w-[18px] afp-h-[18px]'" />
754
+ </button>
755
+
756
+ <div
757
+ #volumeRef
758
+ class="afp-relative"
759
+ (mouseenter)="handleVolumeEnter()"
760
+ (mouseleave)="handleVolumeLeave()"
761
+ >
762
+ <button
763
+ type="button"
764
+ class="afp-rounded-full afp-flex afp-items-center afp-justify-center afp-transition-colors audio-ctrl-btn afp-flex-shrink-0"
765
+ [class.afp-w-8]="isCompact()"
766
+ [class.afp-h-8]="isCompact()"
767
+ [class.afp-w-9]="!isCompact()"
768
+ [class.afp-h-9]="!isCompact()"
769
+ [class.afp-bg-accent-soft]="showVolume()"
770
+ [class.afp-text-accent]="showVolume()"
771
+ [class.afp-bg-surface-2]="!showVolume()"
772
+ [class.afp-text-fg-secondary]="!showVolume()"
773
+ [attr.aria-label]="isMuted() ? t('audio.aria.unmute') : t('audio.aria.mute')"
774
+ (click)="toggleMute()"
775
+ >
776
+ <i-lucide [img]="volumeIcon()" [class]="isCompact() ? 'afp-w-3.5 afp-h-3.5' : 'afp-w-4 afp-h-4'" />
777
+ </button>
778
+
779
+ @if (showVolume()) {
780
+ <div
781
+ class="afp-absolute afp-bottom-full afp-mb-2 afp-rounded-xl afp-p-3 afp-border afp-bg-surface-3 afp-border-line afp-volume-popup"
782
+ [attr.style]="volumePopupStyle()"
783
+ (mouseenter)="handleVolumeEnter()"
784
+ (mouseleave)="handleVolumeLeave()"
785
+ >
786
+ <div class="afp-flex afp-flex-col afp-items-center afp-gap-2" style="height: 100px">
787
+ <div class="afp-relative afp-flex afp-items-center afp-justify-center" style="width: 24px; height: 80px">
788
+ <div class="afp-absolute afp-rounded-full afp-bg-surface-2" style="width: 3px; height: 100%"></div>
789
+ <div
790
+ class="afp-absolute afp-bottom-0 afp-rounded-full afp-pointer-events-none"
791
+ [attr.style]="volumeFillStyle()"
792
+ ></div>
793
+ <input
794
+ type="range"
795
+ min="0"
796
+ max="1"
797
+ step="0.01"
798
+ [value]="isMuted() ? 0 : volume()"
799
+ [attr.aria-label]="t('audio.aria.volume')"
800
+ class="volume-slider-vertical afp-absolute"
801
+ style="width: 80px; height: 24px; transform: rotate(-90deg); transform-origin: center center"
802
+ (input)="onVolumeInput($event)"
803
+ />
804
+ </div>
805
+ <span class="afp-text-[10px] afp-tabular-nums afp-text-fg-tertiary">
806
+ {{ Math.round((isMuted() ? 0 : volume()) * 100) }}
807
+ </span>
808
+ </div>
809
+ </div>
810
+ }
811
+ </div>
812
+ </div>
813
+ </div>
814
+ </div>
815
+
816
+ <audio #audioRef [src]="url()" class="afp-hidden"></audio>
817
+ </div>
818
+ }
819
+ `, styles: [".audio-ctrl-btn{border:0;cursor:pointer}.afp-volume-popup{animation:afp-fade-in .2s ease}@keyframes afp-fade-in{0%{opacity:0}to{opacity:1}}\n"] }]
820
+ }], ctorParameters: () => [] });
821
+
822
+ export { AudioRenderer };
823
+ //# sourceMappingURL=eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs.map