@libs-ui/components-audio 0.2.356-42 → 0.2.356-43
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/README.md
CHANGED
|
@@ -1,196 +1,349 @@
|
|
|
1
1
|
# @libs-ui/components-audio
|
|
2
2
|
|
|
3
|
-
> Component
|
|
3
|
+
> Component audio player đầy đủ tính năng cho Angular — hỗ trợ play/pause, seek, volume, mute và download có kiểm soát quyền.
|
|
4
4
|
|
|
5
5
|
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
`LibsUiComponentsAudioComponent` là một standalone Angular component cung cấp giao diện phát audio
|
|
7
|
+
`LibsUiComponentsAudioComponent` là một standalone Angular component cung cấp giao diện phát audio hoàn chỉnh. Component tích hợp thanh tiến độ, điều chỉnh âm lượng dạng hover-reveal, hiển thị thời gian HH:MM:SS và cơ chế download có kiểm soát quyền thông qua callback. Ngoài việc phản ứng trực tiếp qua Output events, component còn cung cấp `IAudioFunctionControlEvent` — một API điều khiển chương trình từ component cha.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- ✅
|
|
12
|
-
- ✅ Thanh tiến độ
|
|
13
|
-
- ✅
|
|
14
|
-
- ✅
|
|
15
|
-
- ✅
|
|
16
|
-
- ✅
|
|
9
|
+
## Tính năng
|
|
10
|
+
|
|
11
|
+
- ✅ Phát/tạm dừng audio với giao diện icon trực quan
|
|
12
|
+
- ✅ Thanh tiến độ tua nhanh/tua lại (seek 0–100%)
|
|
13
|
+
- ✅ Điều chỉnh âm lượng với slider ẩn/hiện theo hover
|
|
14
|
+
- ✅ Bật/tắt tiếng (mute/unmute) đồng bộ với volume slider
|
|
15
|
+
- ✅ Hiển thị thời gian định dạng HH:MM:SS (currentTime / duration)
|
|
16
|
+
- ✅ Download file audio có kiểm soát quyền qua async callback
|
|
17
|
+
- ✅ Function Control API — điều khiển audio từ component cha qua `outFunctionsControl`
|
|
18
|
+
- ✅ Hỗ trợ bàn phím (Enter, Space) cho mọi control
|
|
19
|
+
- ✅ Angular Signals + OnPush Change Detection
|
|
20
|
+
|
|
21
|
+
## Khi nào sử dụng
|
|
22
|
+
|
|
23
|
+
- Cần nhúng audio player có giao diện chuẩn vào trang chi tiết, comment, hoặc tin nhắn
|
|
24
|
+
- Cần điều khiển audio từ bên ngoài component (play, pause, seek, volume) thông qua API
|
|
25
|
+
- Cần tracking trạng thái audio theo thời gian thực (đang phát, mute, vị trí hiện tại)
|
|
26
|
+
- Cần giới hạn quyền download file audio theo logic nghiệp vụ (kiểm tra permission trước khi cho tải)
|
|
27
|
+
- Phát file MP3, WAV, OGG trong ứng dụng web Angular
|
|
17
28
|
|
|
18
29
|
## Cài đặt
|
|
19
30
|
|
|
20
31
|
```bash
|
|
21
|
-
# npm
|
|
22
32
|
npm install @libs-ui/components-audio
|
|
33
|
+
```
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
## Import
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { LibsUiComponentsAudioComponent, IAudioFunctionControlEvent } from '@libs-ui/components-audio';
|
|
26
39
|
```
|
|
27
40
|
|
|
28
|
-
##
|
|
41
|
+
## Ví dụ sử dụng
|
|
29
42
|
|
|
30
|
-
###
|
|
43
|
+
### Ví dụ 1 — Cơ bản (Minimal)
|
|
31
44
|
|
|
32
45
|
```typescript
|
|
33
46
|
import { Component } from '@angular/core';
|
|
47
|
+
import { LibsUiComponentsAudioComponent } from '@libs-ui/components-audio';
|
|
48
|
+
|
|
49
|
+
@Component({
|
|
50
|
+
selector: 'app-audio-basic',
|
|
51
|
+
standalone: true,
|
|
52
|
+
imports: [LibsUiComponentsAudioComponent],
|
|
53
|
+
template: `
|
|
54
|
+
<libs_ui-components-audio
|
|
55
|
+
[fileAudio]="audioUrl"
|
|
56
|
+
[checkPermissionDownloadAudio]="checkDownloadPermission"
|
|
57
|
+
/>
|
|
58
|
+
`,
|
|
59
|
+
})
|
|
60
|
+
export class AudioBasicComponent {
|
|
61
|
+
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
|
|
62
|
+
|
|
63
|
+
checkDownloadPermission = (): Promise<boolean> => Promise.resolve(true);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Ví dụ 2 — Lắng nghe trạng thái qua Output
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { Component, signal } from '@angular/core';
|
|
34
71
|
import { LibsUiComponentsAudioComponent, IAudioFunctionControlEvent } from '@libs-ui/components-audio';
|
|
35
72
|
|
|
36
73
|
@Component({
|
|
37
|
-
selector: 'app-
|
|
74
|
+
selector: 'app-audio-status',
|
|
38
75
|
standalone: true,
|
|
39
76
|
imports: [LibsUiComponentsAudioComponent],
|
|
40
77
|
template: `
|
|
41
78
|
<libs_ui-components-audio
|
|
42
79
|
[fileAudio]="audioUrl"
|
|
43
|
-
[checkPermissionDownloadAudio]="
|
|
44
|
-
(
|
|
45
|
-
(
|
|
46
|
-
(outVolumeControl)="
|
|
47
|
-
(outTimeUpdate)="
|
|
48
|
-
(
|
|
49
|
-
|
|
80
|
+
[checkPermissionDownloadAudio]="checkDownloadPermission"
|
|
81
|
+
(outPlay)="handlerPlay($event)"
|
|
82
|
+
(outMute)="handlerMute($event)"
|
|
83
|
+
(outVolumeControl)="handlerVolume($event)"
|
|
84
|
+
(outTimeUpdate)="handlerTimeUpdate($event)"
|
|
85
|
+
(outEnded)="handlerEnded()"
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<div class="mt-4 text-sm text-gray-600">
|
|
89
|
+
<p>Đang phát: {{ isPlaying() }}</p>
|
|
90
|
+
<p>Tắt tiếng: {{ isMuted() }}</p>
|
|
91
|
+
<p>Âm lượng: {{ volume() }}%</p>
|
|
92
|
+
<p>Thời gian: {{ currentTime() }} / {{ duration() }}</p>
|
|
93
|
+
</div>
|
|
50
94
|
`,
|
|
51
95
|
})
|
|
52
|
-
export class
|
|
53
|
-
audioUrl = 'https://
|
|
54
|
-
controls: IAudioFunctionControlEvent | null = null;
|
|
96
|
+
export class AudioStatusComponent {
|
|
97
|
+
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
|
|
55
98
|
|
|
56
|
-
|
|
99
|
+
isPlaying = signal(false);
|
|
100
|
+
isMuted = signal(false);
|
|
101
|
+
volume = signal(100);
|
|
102
|
+
currentTime = signal('00:00:00');
|
|
103
|
+
duration = signal('00:00:00');
|
|
57
104
|
|
|
58
|
-
|
|
59
|
-
this.controls = event;
|
|
60
|
-
}
|
|
105
|
+
checkDownloadPermission = (): Promise<boolean> => Promise.resolve(true);
|
|
61
106
|
|
|
62
|
-
|
|
63
|
-
|
|
107
|
+
handlerPlay(isPlaying: boolean): void {
|
|
108
|
+
this.isPlaying.set(isPlaying);
|
|
64
109
|
}
|
|
65
110
|
|
|
66
|
-
|
|
67
|
-
|
|
111
|
+
handlerMute(isMuted: boolean): void {
|
|
112
|
+
this.isMuted.set(isMuted);
|
|
68
113
|
}
|
|
69
114
|
|
|
70
|
-
|
|
71
|
-
|
|
115
|
+
handlerVolume(vol: number): void {
|
|
116
|
+
this.volume.set(vol);
|
|
72
117
|
}
|
|
73
118
|
|
|
74
|
-
|
|
75
|
-
|
|
119
|
+
handlerTimeUpdate(time: { currentTime: string; duration: string }): void {
|
|
120
|
+
this.currentTime.set(time.currentTime);
|
|
121
|
+
this.duration.set(time.duration);
|
|
76
122
|
}
|
|
77
123
|
|
|
78
|
-
|
|
79
|
-
|
|
124
|
+
handlerEnded(): void {
|
|
125
|
+
this.isPlaying.set(false);
|
|
80
126
|
}
|
|
81
127
|
}
|
|
82
128
|
```
|
|
83
129
|
|
|
84
|
-
### Điều khiển từ bên ngoài
|
|
85
|
-
|
|
86
|
-
Sử dụng `outFunctionsControl` để lấy reference các hàm điều khiển:
|
|
130
|
+
### Ví dụ 3 — Điều khiển audio từ bên ngoài (External Control API)
|
|
87
131
|
|
|
88
132
|
```typescript
|
|
89
|
-
|
|
90
|
-
|
|
133
|
+
import { Component, signal } from '@angular/core';
|
|
134
|
+
import { LibsUiComponentsAudioComponent, IAudioFunctionControlEvent } from '@libs-ui/components-audio';
|
|
91
135
|
|
|
92
|
-
|
|
93
|
-
|
|
136
|
+
@Component({
|
|
137
|
+
selector: 'app-audio-external',
|
|
138
|
+
standalone: true,
|
|
139
|
+
imports: [LibsUiComponentsAudioComponent],
|
|
140
|
+
template: `
|
|
141
|
+
<libs_ui-components-audio
|
|
142
|
+
[fileAudio]="audioUrl"
|
|
143
|
+
[checkPermissionDownloadAudio]="checkDownloadPermission"
|
|
144
|
+
(outFunctionsControl)="handlerFunctionsControl($event)"
|
|
145
|
+
(outPlay)="isPlaying.set($event)"
|
|
146
|
+
(outMute)="isMuted.set($event)"
|
|
147
|
+
/>
|
|
148
|
+
|
|
149
|
+
<div class="flex gap-2 mt-4">
|
|
150
|
+
<button class="px-4 py-2 bg-blue-500 text-white rounded" (click)="handlerTogglePlay()">
|
|
151
|
+
{{ isPlaying() ? 'Pause' : 'Play' }}
|
|
152
|
+
</button>
|
|
153
|
+
<button class="px-4 py-2 bg-gray-500 text-white rounded" (click)="handlerToggleMute()">
|
|
154
|
+
{{ isMuted() ? 'Unmute' : 'Mute' }}
|
|
155
|
+
</button>
|
|
156
|
+
<button class="px-4 py-2 bg-green-500 text-white rounded" (click)="handlerSetVolume50()">
|
|
157
|
+
Volume 50%
|
|
158
|
+
</button>
|
|
159
|
+
<button class="px-4 py-2 bg-orange-500 text-white rounded" (click)="handlerSeekMidpoint()">
|
|
160
|
+
Seek 50%
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
`,
|
|
164
|
+
})
|
|
165
|
+
export class AudioExternalControlComponent {
|
|
166
|
+
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
|
|
167
|
+
|
|
168
|
+
audioControls = signal<IAudioFunctionControlEvent | null>(null);
|
|
169
|
+
isPlaying = signal(false);
|
|
170
|
+
isMuted = signal(false);
|
|
171
|
+
|
|
172
|
+
checkDownloadPermission = (): Promise<boolean> => Promise.resolve(true);
|
|
173
|
+
|
|
174
|
+
handlerFunctionsControl(controls: IAudioFunctionControlEvent): void {
|
|
175
|
+
this.audioControls.set(controls);
|
|
176
|
+
}
|
|
94
177
|
|
|
95
|
-
|
|
96
|
-
this.
|
|
178
|
+
handlerTogglePlay(): void {
|
|
179
|
+
this.audioControls()?.playPause();
|
|
180
|
+
}
|
|
97
181
|
|
|
98
|
-
|
|
99
|
-
this.
|
|
182
|
+
handlerToggleMute(): void {
|
|
183
|
+
this.audioControls()?.toggleMute();
|
|
184
|
+
}
|
|
100
185
|
|
|
101
|
-
|
|
102
|
-
this.
|
|
186
|
+
handlerSetVolume50(): void {
|
|
187
|
+
this.audioControls()?.setVolume(50);
|
|
188
|
+
}
|
|
103
189
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
190
|
+
handlerSeekMidpoint(): void {
|
|
191
|
+
this.audioControls()?.seekTo(50);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
107
194
|
```
|
|
108
195
|
|
|
109
|
-
|
|
196
|
+
### Ví dụ 4 — Kiểm soát quyền download theo nghiệp vụ
|
|
110
197
|
|
|
111
|
-
|
|
198
|
+
```typescript
|
|
199
|
+
import { Component, inject } from '@angular/core';
|
|
200
|
+
import { LibsUiComponentsAudioComponent } from '@libs-ui/components-audio';
|
|
112
201
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
202
|
+
@Component({
|
|
203
|
+
selector: 'app-audio-permission',
|
|
204
|
+
standalone: true,
|
|
205
|
+
imports: [LibsUiComponentsAudioComponent],
|
|
206
|
+
template: `
|
|
207
|
+
<libs_ui-components-audio
|
|
208
|
+
[fileAudio]="audioUrl"
|
|
209
|
+
[checkPermissionDownloadAudio]="checkDownloadPermission"
|
|
210
|
+
/>
|
|
211
|
+
`,
|
|
212
|
+
})
|
|
213
|
+
export class AudioPermissionComponent {
|
|
214
|
+
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* checkPermissionDownloadAudio nhận một factory function (() => Promise<boolean>),
|
|
218
|
+
* không phải Promise<boolean> trực tiếp. Component sẽ gọi hàm này mỗi lần user nhấn download.
|
|
219
|
+
*/
|
|
220
|
+
checkDownloadPermission = (): Promise<boolean> => {
|
|
221
|
+
// Ví dụ: kiểm tra role của user trước khi cho phép tải
|
|
222
|
+
const userRole = 'admin'; // lấy từ auth service thực tế
|
|
223
|
+
return Promise.resolve(userRole === 'admin');
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## @Input()
|
|
229
|
+
|
|
230
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
231
|
+
|---|---|---|---|---|
|
|
232
|
+
| `fileAudio` | `string` | **required** | URL của file audio cần phát. Hỗ trợ MP3, WAV, OGG. Khi giá trị thay đổi, audio sẽ tự động reload. | `[fileAudio]="'https://example.com/audio.mp3'"` |
|
|
233
|
+
| `checkPermissionDownloadAudio` | `() => Promise<boolean>` | **required** | Factory function kiểm tra quyền download. Được gọi mỗi lần user nhấn nút tải. Trả về `true` để cho phép, `false` để chặn. | `[checkPermissionDownloadAudio]="checkPermission"` |
|
|
117
234
|
|
|
118
|
-
|
|
235
|
+
## @Output()
|
|
119
236
|
|
|
120
|
-
|
|
|
121
|
-
|
|
122
|
-
| `outFunctionsControl` | `IAudioFunctionControlEvent`
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
237
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
238
|
+
|---|---|---|---|---|
|
|
239
|
+
| `(outFunctionsControl)` | `IAudioFunctionControlEvent` | Emit ngay sau `ngAfterViewInit` — cung cấp API điều khiển audio từ bên ngoài. | `handlerFunctionsControl(e: IAudioFunctionControlEvent): void { e; /* không stopPropagation vì đây là output signal */ this.controls.set(e); }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
|
|
240
|
+
| `(outPlay)` | `boolean` | Emit mỗi khi trạng thái play/pause thay đổi. `true` = đang phát, `false` = đang dừng. | `handlerPlay(e: boolean): void { this.isPlaying.set(e); }` | `(outPlay)="handlerPlay($event)"` |
|
|
241
|
+
| `(outMute)` | `boolean` | Emit mỗi khi trạng thái mute thay đổi. `true` = đang tắt tiếng. | `handlerMute(e: boolean): void { this.isMuted.set(e); }` | `(outMute)="handlerMute($event)"` |
|
|
242
|
+
| `(outVolumeControl)` | `number` | Emit giá trị âm lượng hiện tại từ 0 đến 100, mỗi khi volume slider thay đổi. | `handlerVolume(e: number): void { this.volume.set(e); }` | `(outVolumeControl)="handlerVolume($event)"` |
|
|
243
|
+
| `(outTimeUpdate)` | `{ currentTime: string; duration: string }` | Emit theo từng tick cập nhật thời gian. Định dạng: `'HH:MM:SS'`. | `handlerTimeUpdate(e: { currentTime: string; duration: string }): void { this.currentTime.set(e.currentTime); this.duration.set(e.duration); }` | `(outTimeUpdate)="handlerTimeUpdate($event)"` |
|
|
244
|
+
| `(outEnded)` | `void` | Emit khi audio phát hết đến cuối file. | `handlerEnded(): void { this.isPlaying.set(false); }` | `(outEnded)="handlerEnded()"` |
|
|
128
245
|
|
|
129
|
-
|
|
246
|
+
## Types & Interfaces
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { IAudioFunctionControlEvent } from '@libs-ui/components-audio';
|
|
250
|
+
```
|
|
130
251
|
|
|
131
252
|
```typescript
|
|
132
253
|
interface IAudioFunctionControlEvent {
|
|
133
|
-
/**
|
|
134
|
-
* Bắt đầu hoặc tạm dừng phát audio
|
|
135
|
-
*/
|
|
254
|
+
/** Bắt đầu hoặc tạm dừng phát audio */
|
|
136
255
|
playPause: (event?: Event) => void;
|
|
137
256
|
|
|
138
|
-
/**
|
|
139
|
-
* Bật hoặc tắt âm thanh
|
|
140
|
-
*/
|
|
257
|
+
/** Bật hoặc tắt âm thanh */
|
|
141
258
|
toggleMute: (event?: Event) => void;
|
|
142
259
|
|
|
143
260
|
/**
|
|
144
|
-
* Điều chỉnh âm lượng
|
|
261
|
+
* Điều chỉnh âm lượng
|
|
262
|
+
* @param value Giá trị từ 0 đến 100
|
|
145
263
|
*/
|
|
146
264
|
setVolume: (value: number) => void;
|
|
147
265
|
|
|
148
266
|
/**
|
|
149
|
-
* Di chuyển đến vị trí cụ thể
|
|
267
|
+
* Di chuyển đến vị trí cụ thể trong audio
|
|
268
|
+
* @param value Giá trị phần trăm từ 0 đến 100
|
|
150
269
|
*/
|
|
151
270
|
seekTo: (value: number) => void;
|
|
152
271
|
|
|
153
|
-
/**
|
|
154
|
-
* Tải xuống file audio
|
|
155
|
-
*/
|
|
272
|
+
/** Tải xuống file audio (chỉ thực hiện nếu checkPermissionDownloadAudio trả về true) */
|
|
156
273
|
download: (event?: Event) => void;
|
|
157
274
|
|
|
158
|
-
/**
|
|
159
|
-
* Kiểm tra trạng thái đang phát
|
|
160
|
-
*/
|
|
275
|
+
/** Trả về true nếu audio đang phát */
|
|
161
276
|
isPlaying: () => boolean;
|
|
162
277
|
|
|
163
|
-
/**
|
|
164
|
-
* Kiểm tra trạng thái tắt tiếng
|
|
165
|
-
*/
|
|
278
|
+
/** Trả về true nếu audio đang tắt tiếng */
|
|
166
279
|
isMuted: () => boolean;
|
|
167
280
|
}
|
|
168
281
|
```
|
|
169
282
|
|
|
170
|
-
|
|
283
|
+
### Cách sử dụng IAudioFunctionControlEvent
|
|
171
284
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
- **TailwindCSS** - Styling
|
|
285
|
+
```typescript
|
|
286
|
+
// Nhận controls qua outFunctionsControl
|
|
287
|
+
private audioControls = signal<IAudioFunctionControlEvent | null>(null);
|
|
176
288
|
|
|
177
|
-
|
|
289
|
+
handlerFunctionsControl(controls: IAudioFunctionControlEvent): void {
|
|
290
|
+
this.audioControls.set(controls);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Sau đó gọi từ bất kỳ đâu trong component cha
|
|
294
|
+
handlerPlayPause(): void {
|
|
295
|
+
this.audioControls()?.playPause();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
handlerSetVolumeTo30(): void {
|
|
299
|
+
this.audioControls()?.setVolume(30);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
handlerSeekToBeginning(): void {
|
|
303
|
+
this.audioControls()?.seekTo(0);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
handlerCheckState(): void {
|
|
307
|
+
const playing = this.audioControls()?.isPlaying(); // boolean
|
|
308
|
+
const muted = this.audioControls()?.isMuted(); // boolean
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Lưu ý quan trọng
|
|
313
|
+
|
|
314
|
+
⚠️ **checkPermissionDownloadAudio là factory function, không phải Promise**: Input này nhận `() => Promise<boolean>`, không phải `Promise<boolean>` trực tiếp. Component gọi hàm này khi user nhấn download, không gọi trước. Đảm bảo truyền vào một hàm (arrow function hoặc method reference), không truyền kết quả gọi hàm.
|
|
178
315
|
|
|
179
|
-
|
|
316
|
+
```typescript
|
|
317
|
+
// ❌ SAI — truyền Promise trực tiếp
|
|
318
|
+
[checkPermissionDownloadAudio]="checkPermission()"
|
|
319
|
+
|
|
320
|
+
// ✅ ĐÚNG — truyền function reference
|
|
321
|
+
[checkPermissionDownloadAudio]="checkPermission"
|
|
322
|
+
|
|
323
|
+
// ✅ ĐÚNG — truyền inline arrow function
|
|
324
|
+
[checkPermissionDownloadAudio]="() => permissionService.canDownloadAudio()"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
⚠️ **outFunctionsControl emit một lần sau ngAfterViewInit**: Output này chỉ emit một lần khi component khởi tạo xong. Lưu lại giá trị vào signal hoặc biến class để dùng về sau. Không cần subscribe lại khi `fileAudio` thay đổi — cùng một controls object vẫn hoạt động với file mới.
|
|
328
|
+
|
|
329
|
+
⚠️ **Định dạng thời gian outTimeUpdate luôn là HH:MM:SS**: Ngay cả khi duration dưới 1 giờ, output vẫn là `'00:03:45'` (không phải `'3:45'`). Lưu ý điều này khi so sánh hoặc hiển thị thời gian.
|
|
330
|
+
|
|
331
|
+
⚠️ **Audio không tự phát khi fileAudio thay đổi**: Khi `fileAudio` input thay đổi, component reload audio nhưng không tự động phát. Gọi `controls.playPause()` sau khi cần thiết.
|
|
332
|
+
|
|
333
|
+
## Demo
|
|
180
334
|
|
|
181
335
|
```bash
|
|
182
336
|
npx nx serve core-ui
|
|
183
337
|
```
|
|
184
338
|
|
|
185
|
-
**File demo:** `apps/core-ui/src/app/components/audio/audio.component.ts`
|
|
186
|
-
|
|
187
339
|
Truy cập: `http://localhost:4500/audio`
|
|
188
340
|
|
|
189
|
-
|
|
341
|
+
File demo: `apps/core-ui/src/app/components/audio/audio.component.ts`
|
|
190
342
|
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
343
|
+
Các ví dụ có trong demo:
|
|
344
|
+
- Basic Usage — cách dùng tối thiểu
|
|
345
|
+
- Status Tracking — theo dõi trạng thái play, mute, volume, time
|
|
346
|
+
- External Control — điều khiển audio hoàn toàn từ bên ngoài component
|
|
194
347
|
|
|
195
348
|
## Unit Tests
|
|
196
349
|
|
|
@@ -198,13 +351,9 @@ Truy cập: `http://localhost:4500/audio`
|
|
|
198
351
|
# Chạy tests
|
|
199
352
|
npx nx test components-audio
|
|
200
353
|
|
|
201
|
-
# Chạy
|
|
354
|
+
# Chạy với coverage
|
|
202
355
|
npx nx test components-audio --coverage
|
|
203
356
|
|
|
204
|
-
#
|
|
205
|
-
npx nx test components-audio --
|
|
357
|
+
# Chạy một file spec cụ thể
|
|
358
|
+
npx nx test components-audio --testFile=libs-ui/components/audio/src/audio.component.spec.ts
|
|
206
359
|
```
|
|
207
|
-
|
|
208
|
-
## License
|
|
209
|
-
|
|
210
|
-
MIT
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, input, viewChild, output, inject, DestroyRef, effect,
|
|
2
|
+
import { signal, input, viewChild, output, inject, DestroyRef, effect, Component, ChangeDetectionStrategy } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
4
|
import { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';
|
|
5
5
|
import { merge, tap, fromEvent } from 'rxjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libs-ui-components-audio.mjs","sources":["../../../../../libs-ui/components/audio/src/audio.component.ts","../../../../../libs-ui/components/audio/src/audio.component.html","../../../../../libs-ui/components/audio/src/libs-ui-components-audio.ts"],"sourcesContent":["import { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, effect, ElementRef, inject, input, output, signal, viewChild } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';\nimport { fromEvent, merge, Observable, tap } from 'rxjs';\nimport { IAudioFunctionControlEvent } from './interfaces/function-control-event.interface';\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-audio',\n templateUrl: './audio.component.html',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LibsUiComponentsInputsRangeSliderComponent],\n})\nexport class LibsUiComponentsAudioComponent implements AfterViewInit {\n // =========================================\n // INTERNAL SIGNALS\n // =========================================\n\n protected audioRatioValue = signal<number>(0);\n\n protected volumeRatioValue = signal<number>(100);\n\n protected isPlay = signal<boolean>(false);\n\n protected isMute = signal<boolean>(false);\n\n protected isSliderAudioPress = signal<boolean>(false);\n\n protected isDisable = signal<boolean>(true);\n\n protected audioTimeCurrent = signal<string>('_:_:_');\n\n protected audioTimeDuration = signal<string>('_:_:_');\n\n protected showFullControlVolume = signal<boolean>(false);\n\n // =========================================\n // INPUTS\n // =========================================\n\n readonly fileAudio = input.required<string>();\n\n readonly checkPermissionDownloadAudio = input.required<() => Promise<boolean>>();\n\n // =========================================\n // VIEW CHILDREN\n // =========================================\n\n readonly audioRef = viewChild.required<ElementRef>('audioRef');\n\n readonly volumeControlRef = viewChild.required<ElementRef>('volumeControlRef');\n\n // =========================================\n // OUTPUTS\n // =========================================\n\n readonly outFunctionsControl = output<IAudioFunctionControlEvent>();\n\n readonly outVolumeControl = output<number>();\n\n readonly outTimeUpdate = output<{ currentTime: string; duration: string }>();\n\n readonly outEnded = output<void>();\n\n readonly outMute = output<boolean>();\n\n readonly outPlay = output<boolean>();\n\n // =========================================\n // PRIVATE PROPERTIES\n // =========================================\n\n private readonly destroyRef = inject(DestroyRef);\n\n // =========================================\n // CONSTRUCTOR\n // =========================================\n\n constructor() {\n // Watch for file audio changes\n effect(() => {\n if (this.fileAudio() && this.audioRef()) {\n setTimeout(() => {\n this.audioRef().nativeElement.load();\n }, 0);\n }\n });\n\n effect(() => {\n this.outVolumeControl.emit(this.volumeRatioValue());\n });\n\n effect(() => {\n this.outTimeUpdate.emit({ currentTime: this.audioTimeCurrent(), duration: this.audioTimeDuration() });\n });\n\n effect(() => {\n this.outMute.emit(this.isMute());\n });\n\n effect(() => {\n this.outPlay.emit(this.isPlay());\n });\n }\n\n // =========================================\n // LIFECYCLE HOOKS\n // =========================================\n\n ngAfterViewInit(): void {\n merge(\n this.initObservable(this.volumeControlRef().nativeElement, 'mouseenter').pipe(tap(() => this.showFullControlVolume.set(true))),\n this.initObservable(this.volumeControlRef().nativeElement, 'mouseleave').pipe(tap(() => this.showFullControlVolume.set(false)))\n )\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe();\n\n // Emit function control event after view is initialized\n this.outFunctionsControl.emit(this.FunctionsControl);\n }\n\n // =========================================\n // PUBLIC API\n // =========================================\n\n public get FunctionsControl(): IAudioFunctionControlEvent {\n return {\n playPause: (event?: Event) => void this.handlerAudioPausePlay(event),\n toggleMute: (event?: Event) => void this.handlerAudioMuteMuted(event),\n seekTo: this.handlerChangeAudio.bind(this),\n setVolume: this.handlerChangeVolume.bind(this),\n download: (event?: Event) => void this.handlerDownload(event),\n isPlaying: () => this.isPlay(),\n isMuted: () => this.isMute(),\n };\n }\n\n // =========================================\n // PRIVATE METHODS\n // =========================================\n\n private initObservable(el: HTMLElement, eventName: string): Observable<MouseEvent> {\n return fromEvent<MouseEvent>(el, eventName).pipe(\n tap((e) => e.stopPropagation()),\n takeUntilDestroyed(this.destroyRef)\n );\n }\n\n protected async handlerKeyPressAudio(): Promise<void> {\n this.isSliderAudioPress.set(true);\n }\n\n protected async handlerAudioMuteMuted(event?: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n if (this.audioRef().nativeElement.muted === true) {\n this.audioRef().nativeElement.muted = false;\n this.isMute.set(false);\n this.volumeRatioValue.set(50);\n return;\n }\n\n this.volumeRatioValue.set(0);\n this.isMute.set(true);\n this.audioRef().nativeElement.muted = true;\n }\n\n protected async handlerAudioPausePlay(event?: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n const audioElement = this.audioRef().nativeElement;\n if (!audioElement.paused) {\n audioElement.pause();\n this.isPlay.set(false);\n return;\n }\n\n try {\n await audioElement.play();\n this.isPlay.set(true);\n } catch (error) {\n console.error('Error playing audio:', error);\n }\n }\n\n protected async handlerLoadedData(event: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n if (this.audioRef().nativeElement) {\n this.audioTimeDuration.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.duration)));\n this.audioTimeCurrent.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.currentTime || 0)));\n this.isDisable.set(false);\n this.isPlay.set(false);\n this.audioRatioValue.set(0);\n this.audioRef().nativeElement.pause();\n }\n }\n\n protected async handlerTimeUpdate(event?: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n this.isDisable.set(!(this.audioRef().nativeElement.duration || 0));\n\n if (!this.audioRef().nativeElement) {\n return;\n }\n\n this.audioTimeDuration.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.duration)));\n this.audioTimeCurrent.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.currentTime || 0)));\n\n if (this.isSliderAudioPress()) {\n this.audioRef().nativeElement.currentTime = (this.audioRatioValue() * Math.floor(this.audioRef().nativeElement.duration || 0)) / 100;\n return;\n }\n\n this.audioRatioValue.set(Math.floor(((this.audioRef().nativeElement.currentTime || 0) / (this.audioRef().nativeElement.duration || 1)) * 100));\n }\n\n /**\n * Format seconds -> HH:MM:SS.\n * Used for both `currentTime` and `duration` outputs to keep output stable.\n */\n private async toHHMMSS(time: number): Promise<string> {\n const hours = Math.floor(time / 3600);\n const minutes = Math.floor((time - hours * 3600) / 60);\n const seconds = time - hours * 3600 - minutes * 60;\n\n const getLabel = (val: number) => {\n val = val || 0;\n return `${val < 10 ? '0' : ''}${val}`;\n };\n\n return `${getLabel(hours)}:${getLabel(minutes)}:${getLabel(seconds)}`;\n }\n\n protected async handlerChangeAudio(value: number): Promise<void> {\n if (value === this.audioRatioValue()) {\n return;\n }\n\n this.audioRef().nativeElement.currentTime = ((value || 0) * (this.audioRef().nativeElement.duration || 0)) / 100;\n this.audioRatioValue.set(value);\n this.isSliderAudioPress.set(false);\n }\n\n protected async handlerChangeVolume(value: number): Promise<void> {\n this.audioRef().nativeElement.volume = value / 100;\n this.volumeRatioValue.set(value);\n\n if (this.audioRef().nativeElement.volume) {\n this.audioRef().nativeElement.muted = false;\n this.isMute.set(false);\n return;\n }\n\n this.audioRef().nativeElement.muted = true;\n this.isMute.set(true);\n }\n\n protected async handlerEnded(event: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n this.isPlay.set(false);\n this.outEnded.emit();\n }\n\n /**\n * Download chỉ được thực hiện nếu callback permission trả về `true`.\n * Lưu ý: `checkPermissionDownloadAudio` là input function (factory), nên cần gọi 2 lần:\n * - lần 1: lấy function\n * - lần 2: execute function để lấy Promise<boolean>\n */\n protected async handlerDownload(e?: Event): Promise<void> {\n if (!this.checkPermissionDownloadAudio() || !(await this.checkPermissionDownloadAudio()())) {\n return;\n }\n\n if (e) {\n e.stopPropagation();\n }\n\n if (!this.fileAudio()) {\n return;\n }\n\n window.open(this.fileAudio(), '_blank');\n }\n}\n","<audio\n controls\n #audioRef\n class=\"hidden\"\n (timeupdate)=\"handlerTimeUpdate($event)\"\n (loadeddata)=\"handlerLoadedData($event)\"\n (ended)=\"handlerEnded($event)\">\n <source\n [src]=\"fileAudio()\"\n type=\"audio/mpeg\" />\n</audio>\n<div\n [class.libs-ui-disable]=\"isDisable()\"\n [class.pointer-events-none]=\"isDisable()\">\n <div class=\"flex justify-between items-center\">\n <div class=\"w-[70%] flex p-0 items-center\">\n <div\n class=\"flex mr-[16px] cursor-pointer\"\n tabindex=\"0\"\n (click)=\"handlerAudioPausePlay($event)\"\n (keydown.enter)=\"handlerAudioPausePlay($event)\"\n (keydown.space)=\"handlerAudioPausePlay($event)\">\n <i\n class=\"text-[16px]\"\n [class.libs-ui-icon-play-solid]=\"!isPlay()\"\n [class.libs-ui-icon-pause-solid]=\"isPlay()\"></i>\n </div>\n <div class=\"libs-ui-font-h5r mr-[16px]\">{{ audioTimeCurrent() }} /{{ audioTimeDuration() }}</div>\n </div>\n <div class=\"w-[30%] flex p-0 items-center justify-end\">\n <div\n #volumeControlRef\n class=\"flex py-[3px] items-center rounded-[12px] h-[28px]\"\n [class.bg-[#e6e7ea]]=\"showFullControlVolume()\"\n [class.px-[12px]]=\"showFullControlVolume()\">\n <i\n class=\"text-[16px] cursor-pointer\"\n tabindex=\"0\"\n [class.libs-ui-icon-speaker-on-solid]=\"!isMute()\"\n [class.libs-ui-icon-speaker-off-solid]=\"isMute()\"\n (click)=\"handlerAudioMuteMuted($event)\"\n (keydown.enter)=\"handlerAudioMuteMuted($event)\"\n (keydown.space)=\"handlerAudioMuteMuted($event)\"></i>\n <libs_ui-components-inputs-range_slider\n [class.hidden]=\"!showFullControlVolume()\"\n [mode]=\"'audio'\"\n classInclude=\"flex items-center !w-[54px] cursor-pointer ml-[8px]\"\n [value]=\"volumeRatioValue()\"\n (outChange)=\"handlerChangeVolume($event)\" />\n </div>\n\n <i\n class=\"libs-ui-icon-download-solid ml-[16px] cursor-pointer\"\n tabindex=\"0\"\n (click)=\"handlerDownload($event)\"\n (keydown.enter)=\"handlerDownload($event)\"\n (keydown.space)=\"handlerDownload($event)\"></i>\n </div>\n </div>\n <div class=\"h-[24px]\">\n <libs_ui-components-inputs-range_slider\n [mode]=\"'audio'\"\n [value]=\"audioRatioValue()\"\n [disable]=\"isDisable()\"\n (outChange)=\"handlerChangeAudio($event)\" />\n </div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"libs-ui-components-audio.mjs","sources":["../../../../../libs-ui/components/audio/src/audio.component.ts","../../../../../libs-ui/components/audio/src/audio.component.html","../../../../../libs-ui/components/audio/src/libs-ui-components-audio.ts"],"sourcesContent":["import { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, effect, ElementRef, inject, input, output, signal, viewChild } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';\nimport { fromEvent, merge, Observable, tap } from 'rxjs';\nimport { IAudioFunctionControlEvent } from './interfaces/function-control-event.interface';\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-audio',\n templateUrl: './audio.component.html',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [LibsUiComponentsInputsRangeSliderComponent],\n})\nexport class LibsUiComponentsAudioComponent implements AfterViewInit {\n // =========================================\n // INTERNAL SIGNALS\n // =========================================\n\n protected audioRatioValue = signal<number>(0);\n\n protected volumeRatioValue = signal<number>(100);\n\n protected isPlay = signal<boolean>(false);\n\n protected isMute = signal<boolean>(false);\n\n protected isSliderAudioPress = signal<boolean>(false);\n\n protected isDisable = signal<boolean>(true);\n\n protected audioTimeCurrent = signal<string>('_:_:_');\n\n protected audioTimeDuration = signal<string>('_:_:_');\n\n protected showFullControlVolume = signal<boolean>(false);\n\n // =========================================\n // INPUTS\n // =========================================\n\n readonly fileAudio = input.required<string>();\n\n readonly checkPermissionDownloadAudio = input.required<() => Promise<boolean>>();\n\n // =========================================\n // VIEW CHILDREN\n // =========================================\n\n readonly audioRef = viewChild.required<ElementRef>('audioRef');\n\n readonly volumeControlRef = viewChild.required<ElementRef>('volumeControlRef');\n\n // =========================================\n // OUTPUTS\n // =========================================\n\n readonly outFunctionsControl = output<IAudioFunctionControlEvent>();\n\n readonly outVolumeControl = output<number>();\n\n readonly outTimeUpdate = output<{ currentTime: string; duration: string }>();\n\n readonly outEnded = output<void>();\n\n readonly outMute = output<boolean>();\n\n readonly outPlay = output<boolean>();\n\n // =========================================\n // PRIVATE PROPERTIES\n // =========================================\n\n private readonly destroyRef = inject(DestroyRef);\n\n // =========================================\n // CONSTRUCTOR\n // =========================================\n\n constructor() {\n // Watch for file audio changes\n effect(() => {\n if (this.fileAudio() && this.audioRef()) {\n setTimeout(() => {\n this.audioRef().nativeElement.load();\n }, 0);\n }\n });\n\n effect(() => {\n this.outVolumeControl.emit(this.volumeRatioValue());\n });\n\n effect(() => {\n this.outTimeUpdate.emit({ currentTime: this.audioTimeCurrent(), duration: this.audioTimeDuration() });\n });\n\n effect(() => {\n this.outMute.emit(this.isMute());\n });\n\n effect(() => {\n this.outPlay.emit(this.isPlay());\n });\n }\n\n // =========================================\n // LIFECYCLE HOOKS\n // =========================================\n\n ngAfterViewInit(): void {\n merge(\n this.initObservable(this.volumeControlRef().nativeElement, 'mouseenter').pipe(tap(() => this.showFullControlVolume.set(true))),\n this.initObservable(this.volumeControlRef().nativeElement, 'mouseleave').pipe(tap(() => this.showFullControlVolume.set(false)))\n )\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe();\n\n // Emit function control event after view is initialized\n this.outFunctionsControl.emit(this.FunctionsControl);\n }\n\n // =========================================\n // PUBLIC API\n // =========================================\n\n public get FunctionsControl(): IAudioFunctionControlEvent {\n return {\n playPause: (event?: Event) => void this.handlerAudioPausePlay(event),\n toggleMute: (event?: Event) => void this.handlerAudioMuteMuted(event),\n seekTo: this.handlerChangeAudio.bind(this),\n setVolume: this.handlerChangeVolume.bind(this),\n download: (event?: Event) => void this.handlerDownload(event),\n isPlaying: () => this.isPlay(),\n isMuted: () => this.isMute(),\n };\n }\n\n // =========================================\n // PRIVATE METHODS\n // =========================================\n\n private initObservable(el: HTMLElement, eventName: string): Observable<MouseEvent> {\n return fromEvent<MouseEvent>(el, eventName).pipe(\n tap((e) => e.stopPropagation()),\n takeUntilDestroyed(this.destroyRef)\n );\n }\n\n protected async handlerKeyPressAudio(): Promise<void> {\n this.isSliderAudioPress.set(true);\n }\n\n protected async handlerAudioMuteMuted(event?: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n if (this.audioRef().nativeElement.muted === true) {\n this.audioRef().nativeElement.muted = false;\n this.isMute.set(false);\n this.volumeRatioValue.set(50);\n return;\n }\n\n this.volumeRatioValue.set(0);\n this.isMute.set(true);\n this.audioRef().nativeElement.muted = true;\n }\n\n protected async handlerAudioPausePlay(event?: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n const audioElement = this.audioRef().nativeElement;\n if (!audioElement.paused) {\n audioElement.pause();\n this.isPlay.set(false);\n return;\n }\n\n try {\n await audioElement.play();\n this.isPlay.set(true);\n } catch (error) {\n console.error('Error playing audio:', error);\n }\n }\n\n protected async handlerLoadedData(event: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n if (this.audioRef().nativeElement) {\n this.audioTimeDuration.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.duration)));\n this.audioTimeCurrent.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.currentTime || 0)));\n this.isDisable.set(false);\n this.isPlay.set(false);\n this.audioRatioValue.set(0);\n this.audioRef().nativeElement.pause();\n }\n }\n\n protected async handlerTimeUpdate(event?: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n this.isDisable.set(!(this.audioRef().nativeElement.duration || 0));\n\n if (!this.audioRef().nativeElement) {\n return;\n }\n\n this.audioTimeDuration.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.duration)));\n this.audioTimeCurrent.set(await this.toHHMMSS(Math.floor(this.audioRef().nativeElement.currentTime || 0)));\n\n if (this.isSliderAudioPress()) {\n this.audioRef().nativeElement.currentTime = (this.audioRatioValue() * Math.floor(this.audioRef().nativeElement.duration || 0)) / 100;\n return;\n }\n\n this.audioRatioValue.set(Math.floor(((this.audioRef().nativeElement.currentTime || 0) / (this.audioRef().nativeElement.duration || 1)) * 100));\n }\n\n /**\n * Format seconds -> HH:MM:SS.\n * Used for both `currentTime` and `duration` outputs to keep output stable.\n */\n private async toHHMMSS(time: number): Promise<string> {\n const hours = Math.floor(time / 3600);\n const minutes = Math.floor((time - hours * 3600) / 60);\n const seconds = time - hours * 3600 - minutes * 60;\n\n const getLabel = (val: number) => {\n val = val || 0;\n return `${val < 10 ? '0' : ''}${val}`;\n };\n\n return `${getLabel(hours)}:${getLabel(minutes)}:${getLabel(seconds)}`;\n }\n\n protected async handlerChangeAudio(value: number): Promise<void> {\n if (value === this.audioRatioValue()) {\n return;\n }\n\n this.audioRef().nativeElement.currentTime = ((value || 0) * (this.audioRef().nativeElement.duration || 0)) / 100;\n this.audioRatioValue.set(value);\n this.isSliderAudioPress.set(false);\n }\n\n protected async handlerChangeVolume(value: number): Promise<void> {\n this.audioRef().nativeElement.volume = value / 100;\n this.volumeRatioValue.set(value);\n\n if (this.audioRef().nativeElement.volume) {\n this.audioRef().nativeElement.muted = false;\n this.isMute.set(false);\n return;\n }\n\n this.audioRef().nativeElement.muted = true;\n this.isMute.set(true);\n }\n\n protected async handlerEnded(event: Event): Promise<void> {\n if (event) {\n event.stopPropagation();\n }\n\n this.isPlay.set(false);\n this.outEnded.emit();\n }\n\n /**\n * Download chỉ được thực hiện nếu callback permission trả về `true`.\n * Lưu ý: `checkPermissionDownloadAudio` là input function (factory), nên cần gọi 2 lần:\n * - lần 1: lấy function\n * - lần 2: execute function để lấy Promise<boolean>\n */\n protected async handlerDownload(e?: Event): Promise<void> {\n if (!this.checkPermissionDownloadAudio() || !(await this.checkPermissionDownloadAudio()())) {\n return;\n }\n\n if (e) {\n e.stopPropagation();\n }\n\n if (!this.fileAudio()) {\n return;\n }\n\n window.open(this.fileAudio(), '_blank');\n }\n}\n","<audio\n controls\n #audioRef\n class=\"hidden\"\n (timeupdate)=\"handlerTimeUpdate($event)\"\n (loadeddata)=\"handlerLoadedData($event)\"\n (ended)=\"handlerEnded($event)\">\n <source\n [src]=\"fileAudio()\"\n type=\"audio/mpeg\" />\n</audio>\n<div\n [class.libs-ui-disable]=\"isDisable()\"\n [class.pointer-events-none]=\"isDisable()\">\n <div class=\"flex justify-between items-center\">\n <div class=\"w-[70%] flex p-0 items-center\">\n <div\n class=\"flex mr-[16px] cursor-pointer\"\n tabindex=\"0\"\n (click)=\"handlerAudioPausePlay($event)\"\n (keydown.enter)=\"handlerAudioPausePlay($event)\"\n (keydown.space)=\"handlerAudioPausePlay($event)\">\n <i\n class=\"text-[16px]\"\n [class.libs-ui-icon-play-solid]=\"!isPlay()\"\n [class.libs-ui-icon-pause-solid]=\"isPlay()\"></i>\n </div>\n <div class=\"libs-ui-font-h5r mr-[16px]\">{{ audioTimeCurrent() }} /{{ audioTimeDuration() }}</div>\n </div>\n <div class=\"w-[30%] flex p-0 items-center justify-end\">\n <div\n #volumeControlRef\n class=\"flex py-[3px] items-center rounded-[12px] h-[28px]\"\n [class.bg-[#e6e7ea]]=\"showFullControlVolume()\"\n [class.px-[12px]]=\"showFullControlVolume()\">\n <i\n class=\"text-[16px] cursor-pointer\"\n tabindex=\"0\"\n [class.libs-ui-icon-speaker-on-solid]=\"!isMute()\"\n [class.libs-ui-icon-speaker-off-solid]=\"isMute()\"\n (click)=\"handlerAudioMuteMuted($event)\"\n (keydown.enter)=\"handlerAudioMuteMuted($event)\"\n (keydown.space)=\"handlerAudioMuteMuted($event)\"></i>\n <libs_ui-components-inputs-range_slider\n [class.hidden]=\"!showFullControlVolume()\"\n [mode]=\"'audio'\"\n classInclude=\"flex items-center !w-[54px] cursor-pointer ml-[8px]\"\n [value]=\"volumeRatioValue()\"\n (outChange)=\"handlerChangeVolume($event)\" />\n </div>\n\n <i\n class=\"libs-ui-icon-download-solid ml-[16px] cursor-pointer\"\n tabindex=\"0\"\n (click)=\"handlerDownload($event)\"\n (keydown.enter)=\"handlerDownload($event)\"\n (keydown.space)=\"handlerDownload($event)\"></i>\n </div>\n </div>\n <div class=\"h-[24px]\">\n <libs_ui-components-inputs-range_slider\n [mode]=\"'audio'\"\n [value]=\"audioRatioValue()\"\n [disable]=\"isDisable()\"\n (outChange)=\"handlerChangeAudio($event)\" />\n </div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libs-ui/components-audio",
|
|
3
|
-
"version": "0.2.356-
|
|
3
|
+
"version": "0.2.356-43",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/core": ">=18.0.0",
|
|
6
|
-
"@libs-ui/components-inputs-range-slider": "0.2.356-
|
|
6
|
+
"@libs-ui/components-inputs-range-slider": "0.2.356-43",
|
|
7
7
|
"rxjs": "~7.8.0"
|
|
8
8
|
},
|
|
9
9
|
"sideEffects": false,
|