@libs-ui/components-audio
v0.2.357-10
Published
> 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.
Readme
@libs-ui/components-audio
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.
Giới thiệu
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.
Tính năng
- ✅ Phát/tạm dừng audio với giao diện icon trực quan
- ✅ Thanh tiến độ tua nhanh/tua lại (seek 0–100%)
- ✅ Điều chỉnh âm lượng với slider ẩn/hiện theo hover
- ✅ Bật/tắt tiếng (mute/unmute) đồng bộ với volume slider
- ✅ Hiển thị thời gian định dạng HH:MM:SS (currentTime / duration)
- ✅ Download file audio có kiểm soát quyền qua async callback
- ✅ Function Control API — điều khiển audio từ component cha qua
outFunctionsControl - ✅ Hỗ trợ bàn phím (Enter, Space) cho mọi control
- ✅ Angular Signals + OnPush Change Detection
Khi nào sử dụng
- Cần nhúng audio player có giao diện chuẩn vào trang chi tiết, comment, hoặc tin nhắn
- Cần điều khiển audio từ bên ngoài component (play, pause, seek, volume) thông qua API
- Cần tracking trạng thái audio theo thời gian thực (đang phát, mute, vị trí hiện tại)
- 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)
- Phát file MP3, WAV, OGG trong ứng dụng web Angular
Cài đặt
npm install @libs-ui/components-audioImport
import { LibsUiComponentsAudioComponent, IAudioFunctionControlEvent } from '@libs-ui/components-audio';Ví dụ sử dụng
Ví dụ 1 — Cơ bản (Minimal)
import { Component } from '@angular/core';
import { LibsUiComponentsAudioComponent } from '@libs-ui/components-audio';
@Component({
selector: 'app-audio-basic',
standalone: true,
imports: [LibsUiComponentsAudioComponent],
template: `
<libs_ui-components-audio
[fileAudio]="audioUrl"
[checkPermissionDownloadAudio]="checkDownloadPermission"
/>
`,
})
export class AudioBasicComponent {
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
checkDownloadPermission = (): Promise<boolean> => Promise.resolve(true);
}Ví dụ 2 — Lắng nghe trạng thái qua Output
import { Component, signal } from '@angular/core';
import { LibsUiComponentsAudioComponent, IAudioFunctionControlEvent } from '@libs-ui/components-audio';
@Component({
selector: 'app-audio-status',
standalone: true,
imports: [LibsUiComponentsAudioComponent],
template: `
<libs_ui-components-audio
[fileAudio]="audioUrl"
[checkPermissionDownloadAudio]="checkDownloadPermission"
(outPlay)="handlerPlay($event)"
(outMute)="handlerMute($event)"
(outVolumeControl)="handlerVolume($event)"
(outTimeUpdate)="handlerTimeUpdate($event)"
(outEnded)="handlerEnded()"
/>
<div class="mt-4 text-sm text-gray-600">
<p>Đang phát: {{ isPlaying() }}</p>
<p>Tắt tiếng: {{ isMuted() }}</p>
<p>Âm lượng: {{ volume() }}%</p>
<p>Thời gian: {{ currentTime() }} / {{ duration() }}</p>
</div>
`,
})
export class AudioStatusComponent {
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
isPlaying = signal(false);
isMuted = signal(false);
volume = signal(100);
currentTime = signal('00:00:00');
duration = signal('00:00:00');
checkDownloadPermission = (): Promise<boolean> => Promise.resolve(true);
handlerPlay(isPlaying: boolean): void {
this.isPlaying.set(isPlaying);
}
handlerMute(isMuted: boolean): void {
this.isMuted.set(isMuted);
}
handlerVolume(vol: number): void {
this.volume.set(vol);
}
handlerTimeUpdate(time: { currentTime: string; duration: string }): void {
this.currentTime.set(time.currentTime);
this.duration.set(time.duration);
}
handlerEnded(): void {
this.isPlaying.set(false);
}
}Ví dụ 3 — Điều khiển audio từ bên ngoài (External Control API)
import { Component, signal } from '@angular/core';
import { LibsUiComponentsAudioComponent, IAudioFunctionControlEvent } from '@libs-ui/components-audio';
@Component({
selector: 'app-audio-external',
standalone: true,
imports: [LibsUiComponentsAudioComponent],
template: `
<libs_ui-components-audio
[fileAudio]="audioUrl"
[checkPermissionDownloadAudio]="checkDownloadPermission"
(outFunctionsControl)="handlerFunctionsControl($event)"
(outPlay)="isPlaying.set($event)"
(outMute)="isMuted.set($event)"
/>
<div class="flex gap-2 mt-4">
<button class="px-4 py-2 bg-blue-500 text-white rounded" (click)="handlerTogglePlay()">
{{ isPlaying() ? 'Pause' : 'Play' }}
</button>
<button class="px-4 py-2 bg-gray-500 text-white rounded" (click)="handlerToggleMute()">
{{ isMuted() ? 'Unmute' : 'Mute' }}
</button>
<button class="px-4 py-2 bg-green-500 text-white rounded" (click)="handlerSetVolume50()">
Volume 50%
</button>
<button class="px-4 py-2 bg-orange-500 text-white rounded" (click)="handlerSeekMidpoint()">
Seek 50%
</button>
</div>
`,
})
export class AudioExternalControlComponent {
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
audioControls = signal<IAudioFunctionControlEvent | null>(null);
isPlaying = signal(false);
isMuted = signal(false);
checkDownloadPermission = (): Promise<boolean> => Promise.resolve(true);
handlerFunctionsControl(controls: IAudioFunctionControlEvent): void {
this.audioControls.set(controls);
}
handlerTogglePlay(): void {
this.audioControls()?.playPause();
}
handlerToggleMute(): void {
this.audioControls()?.toggleMute();
}
handlerSetVolume50(): void {
this.audioControls()?.setVolume(50);
}
handlerSeekMidpoint(): void {
this.audioControls()?.seekTo(50);
}
}Ví dụ 4 — Kiểm soát quyền download theo nghiệp vụ
import { Component, inject } from '@angular/core';
import { LibsUiComponentsAudioComponent } from '@libs-ui/components-audio';
@Component({
selector: 'app-audio-permission',
standalone: true,
imports: [LibsUiComponentsAudioComponent],
template: `
<libs_ui-components-audio
[fileAudio]="audioUrl"
[checkPermissionDownloadAudio]="checkDownloadPermission"
/>
`,
})
export class AudioPermissionComponent {
audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3';
/**
* checkPermissionDownloadAudio nhận một factory function (() => Promise<boolean>),
* 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.
*/
checkDownloadPermission = (): Promise<boolean> => {
// Ví dụ: kiểm tra role của user trước khi cho phép tải
const userRole = 'admin'; // lấy từ auth service thực tế
return Promise.resolve(userRole === 'admin');
};
}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| 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'" |
| 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" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (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)" |
| (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)" |
| (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)" |
| (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)" |
| (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)" |
| (outEnded) | void | Emit khi audio phát hết đến cuối file. | handlerEnded(): void { this.isPlaying.set(false); } | (outEnded)="handlerEnded()" |
Types & Interfaces
import { IAudioFunctionControlEvent } from '@libs-ui/components-audio';interface IAudioFunctionControlEvent {
/** Bắt đầu hoặc tạm dừng phát audio */
playPause: (event?: Event) => void;
/** Bật hoặc tắt âm thanh */
toggleMute: (event?: Event) => void;
/**
* Điều chỉnh âm lượng
* @param value Giá trị từ 0 đến 100
*/
setVolume: (value: number) => void;
/**
* Di chuyển đến vị trí cụ thể trong audio
* @param value Giá trị phần trăm từ 0 đến 100
*/
seekTo: (value: number) => void;
/** Tải xuống file audio (chỉ thực hiện nếu checkPermissionDownloadAudio trả về true) */
download: (event?: Event) => void;
/** Trả về true nếu audio đang phát */
isPlaying: () => boolean;
/** Trả về true nếu audio đang tắt tiếng */
isMuted: () => boolean;
}Cách sử dụng IAudioFunctionControlEvent
// Nhận controls qua outFunctionsControl
private audioControls = signal<IAudioFunctionControlEvent | null>(null);
handlerFunctionsControl(controls: IAudioFunctionControlEvent): void {
this.audioControls.set(controls);
}
// Sau đó gọi từ bất kỳ đâu trong component cha
handlerPlayPause(): void {
this.audioControls()?.playPause();
}
handlerSetVolumeTo30(): void {
this.audioControls()?.setVolume(30);
}
handlerSeekToBeginning(): void {
this.audioControls()?.seekTo(0);
}
handlerCheckState(): void {
const playing = this.audioControls()?.isPlaying(); // boolean
const muted = this.audioControls()?.isMuted(); // boolean
}Lưu ý quan trọng
⚠️ 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.
// ❌ SAI — truyền Promise trực tiếp
[checkPermissionDownloadAudio]="checkPermission()"
// ✅ ĐÚNG — truyền function reference
[checkPermissionDownloadAudio]="checkPermission"
// ✅ ĐÚNG — truyền inline arrow function
[checkPermissionDownloadAudio]="() => permissionService.canDownloadAudio()"⚠️ 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.
⚠️ Đị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.
⚠️ 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.
Unit Tests
# Chạy tests
npx nx test components-audio
# Chạy với coverage
npx nx test components-audio --coverage
# Chạy một file spec cụ thể
npx nx test components-audio --testFile=libs-ui/components/audio/src/audio.component.spec.ts