@sixbell-telco/sdk
Version:
A collection of reusable components designed for use in Sixbell Telco Angular projects
544 lines (535 loc) • 48.8 kB
JavaScript
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, signal, computed, Injectable, input, output, ChangeDetectionStrategy, Component, viewChild, HostListener, model, effect } from '@angular/core';
import { matPlayArrow, matPause, matSkipPrevious, matSkipNext } from '@ng-icons/material-icons/baseline';
import { ButtonComponent } from '@sixbell-telco/sdk/components/button';
import { IconComponent } from '@sixbell-telco/sdk/components/icon';
import { heroMusicalNoteMini } from '@sixbell-telco/sdk/components/icon/heroicons/mini';
import { cn } from '@sixbell-telco/sdk/utils/cn';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { TranslatePipe } from '@ngx-translate/core';
import { TypographyDirective } from '@sixbell-telco/sdk/directives/typography';
import { TimePipe } from '@sixbell-telco/sdk/utils/pipes/time';
import { matVolumeUpOutline, matVolumeDownOutline, matVolumeMuteOutline } from '@ng-icons/material-icons/outline';
// audio-player.service.ts
class AudioPlayerService {
audio = new Audio();
http = inject(HttpClient);
objectUrls = new Map();
// State signals
_tracks = signal([]);
_currentTrackIndex = signal(-1);
_isPlaying = signal(false);
_duration = signal(0);
_currentTime = signal(0);
_volume = signal(1);
_isMuted = signal(false);
_lastVolume = signal(1);
_isBuffering = signal(false);
_isInitializing = signal(false);
_autoPlayNext = signal(false);
_error = signal(null);
// Readonly signals
tracks = this._tracks.asReadonly();
currentTrackIndex = this._currentTrackIndex.asReadonly();
isPlaying = this._isPlaying.asReadonly();
duration = this._duration.asReadonly();
currentTime = this._currentTime.asReadonly();
volume = this._volume.asReadonly();
isMuted = this._isMuted.asReadonly();
isBuffering = this._isBuffering.asReadonly();
isInitializing = this._isInitializing.asReadonly();
currentTrack = computed(() => this.tracks()[this.currentTrackIndex()] || undefined);
autoPlayNext = this._autoPlayNext.asReadonly();
error = this._error.asReadonly();
constructor() {
this.setupAudioListeners();
this.restoreVolumeSettings();
}
setupAudioListeners() {
this.audio.addEventListener('loadedmetadata', () => this.updateDuration());
this.audio.addEventListener('timeupdate', () => this.updateTime());
this.audio.addEventListener('playing', () => this.setPlaying(true));
this.audio.addEventListener('pause', () => this.setPlaying(false));
this.audio.addEventListener('ended', () => this.handleEnded());
this.audio.addEventListener('waiting', () => this._isBuffering.set(true));
this.audio.addEventListener('error', (e) => {
this._error.set(e);
console.error('Audio error:', e);
});
}
async getAuthenticatedAudioUrl(track) {
if (!this.objectUrls.has(track.url)) {
const blob = await firstValueFrom(this.http.get(track.url, {
responseType: 'blob',
headers: { Accept: 'audio/mpeg' },
}));
this.objectUrls.set(track.url, URL.createObjectURL(blob));
}
return this.objectUrls.get(track.url);
}
async playTrack(index) {
if (index < 0 || index >= this.tracks().length)
return;
this._isInitializing.set(true);
this._currentTrackIndex.set(index);
const track = this.tracks()[index];
// Get authenticated URL
const authenticatedUrl = await this.getAuthenticatedAudioUrl(track);
// Set audio source
this.audio.src = authenticatedUrl;
this.audio.load();
try {
// Wait for audio to be ready
await new Promise((resolve, reject) => {
this.audio.addEventListener('canplay', () => resolve(), { once: true });
this.audio.addEventListener('error', (e) => reject(e), { once: true });
});
await this.audio.play();
}
catch (error) {
console.error('Error playing track:', error);
this._error.set(error);
this._isPlaying.set(false);
}
finally {
this._isInitializing.set(false);
}
}
init() {
this.audio = new Audio();
this.setupAudioListeners();
this.restoreVolumeSettings();
}
cleanup() {
this.audio.pause();
this.audio.src = '';
this.objectUrls.forEach((url) => URL.revokeObjectURL(url));
this.objectUrls.clear();
// reset signals
this._tracks.set([]);
this._currentTrackIndex.set(-1);
this._isPlaying.set(false);
this._duration.set(0);
this._currentTime.set(0);
this._volume.set(1);
this._isMuted.set(false);
this._lastVolume.set(1);
this._isBuffering.set(false);
this._isInitializing.set(false);
this._autoPlayNext.set(false);
this._error.set(null);
}
async togglePlay() {
if (!this.checkIfTrackIsLoaded())
return;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.audio.paused ? await this.audio.play() : this.audio.pause();
}
async previous() {
this.clearCurrentTime();
const totalTracks = this.tracks().length;
if (totalTracks === 0)
return;
const newIndex = (this.currentTrackIndex() - 1 + totalTracks) % totalTracks;
await this.playTrack(newIndex);
}
async next() {
this.clearCurrentTime();
const totalTracks = this.tracks().length;
if (totalTracks === 0)
return;
const newIndex = (this.currentTrackIndex() + 1) % totalTracks;
await this.playTrack(newIndex);
}
setTracks(tracks) {
this._tracks.set(tracks);
}
setCurrentTrackIndex(index) {
this._currentTrackIndex.set(index);
}
setPlaying(playing) {
this._isPlaying.set(playing);
this._isBuffering.set(false);
}
setAutoPlayNext(autoPlayNext) {
this._autoPlayNext.set(autoPlayNext);
}
setVolume(volume) {
this._volume.set(volume);
this.audio.volume = volume;
if (volume > 0)
this._lastVolume.set(volume);
localStorage.setItem('volume', JSON.stringify(this.volume()));
}
setMuted(muted) {
this._isMuted.set(muted);
if (muted) {
this._lastVolume.set(this.volume());
this.setVolume(0);
}
else {
this.setVolume(this._lastVolume());
}
localStorage.setItem('isMuted', JSON.stringify(this.isMuted()));
}
seekTo(time) {
this.audio.currentTime = time;
}
isPlayingATrack() {
return this.audio.src !== '' && !this.audio.paused;
}
// Private handlers
updateDuration() {
this._duration.set(this.audio.duration);
this._isInitializing.set(false);
}
updateTime() {
this._currentTime.set(this.audio.currentTime);
}
handleEnded() {
this._isPlaying.set(false);
if (this._autoPlayNext())
this.next();
}
clearCurrentTime() {
this.audio.currentTime = 0;
this._currentTime.set(0);
}
restoreVolumeSettings() {
const volume = localStorage.getItem('volume');
const isMuted = localStorage.getItem('isMuted');
if (volume)
this.setVolume(JSON.parse(volume));
if (isMuted)
this.setMuted(JSON.parse(isMuted));
}
checkIfTrackIsLoaded() {
return this.audio.src !== '';
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AudioPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AudioPlayerService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AudioPlayerService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
class ButtonPlayComponent {
audioService = inject(AudioPlayerService);
variant = input();
ghost = input(false);
outline = input(false);
circle = input(false);
square = input(false);
glass = input(false);
size = input();
shadow = input(false);
asButton = input(false);
isHovered = signal(false);
trackIndex = input.required();
togglePlay = output();
iconMusicNote = heroMusicalNoteMini;
iconPlay = matPlayArrow;
iconPause = matPause;
waveBars = [
{ delay: '0.4s', class: 'h-1/5 animate-wave w-[2px] rounded-full bg-current' },
{ delay: '0.3s', class: 'h-3/5 animate-wave w-[2px] rounded-full bg-current' },
{ delay: '0.2s', class: 'h-full animate-wave w-[2px] rounded-full bg-current' },
{ delay: '0.3s', class: 'h-3/5 animate-wave w-[2px] rounded-full bg-current' },
{ delay: '0.4s', class: 'h-1/5 animate-wave w-[2px] rounded-full bg-current' },
];
buttonClass = computed(() => {
return cn('absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 transition-opacity duration-200 inline-flex items-center justify-center align-top', {
'opacity-100': (this.audioService.isPlaying() && this.trackIndex() === this.audioService.currentTrackIndex()) || this.isHovered(),
});
});
waveBarsClass = computed(() => {
return cn('h-6 w-6 opacity-0 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transition-opacity duration-200', {
'opacity-100': this.audioService.isPlaying() && this.trackIndex() === this.audioService.currentTrackIndex() && !this.isHovered(),
});
});
iconClass = computed(() => {
return cn('opacity-0 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transition-opacity duration-200 inline-flex items-center justify-center align-top text-lg', {
'opacity-100': !this.audioService.isPlaying() || this.trackIndex() !== this.audioService.currentTrackIndex() || this.isHovered(),
});
});
icon = computed(() => {
return this.audioService.isPlaying() && this.trackIndex() === this.audioService.currentTrackIndex() ? this.iconPause : this.iconPlay;
});
handleHover(isHovered) {
this.isHovered.set(isHovered);
}
async handleTogglePlay() {
if (this.trackIndex() !== undefined && this.trackIndex() !== this.audioService.currentTrackIndex()) {
await this.audioService.playTrack(this.trackIndex());
}
else {
await this.audioService.togglePlay();
}
this.togglePlay.emit();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: ButtonPlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: ButtonPlayComponent, isStandalone: true, selector: "st-button-play", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, ghost: { classPropertyName: "ghost", publicName: "ghost", isSignal: true, isRequired: false, transformFunction: null }, outline: { classPropertyName: "outline", publicName: "outline", isSignal: true, isRequired: false, transformFunction: null }, circle: { classPropertyName: "circle", publicName: "circle", isSignal: true, isRequired: false, transformFunction: null }, square: { classPropertyName: "square", publicName: "square", isSignal: true, isRequired: false, transformFunction: null }, glass: { classPropertyName: "glass", publicName: "glass", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null }, asButton: { classPropertyName: "asButton", publicName: "asButton", isSignal: true, isRequired: false, transformFunction: null }, trackIndex: { classPropertyName: "trackIndex", publicName: "trackIndex", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { togglePlay: "togglePlay" }, ngImport: i0, template: "@let waveBarsVar = waveBarsClass();\n@let iconClassVar = iconClass();\n@let iconVar = icon();\n\n@if (!asButton()) {\n\t<div\n\t\tclass=\"from-primary to-primary-gradient relative grid h-10 min-h-10 w-10 min-w-10 place-content-center rounded-sm bg-linear-to-br text-lg text-white\"\n\t\t(mouseenter)=\"handleHover(true)\"\n\t\t(mouseleave)=\"handleHover(false)\"\n\t>\n\t\t<st-icon [icon]=\"iconMusicNote\"></st-icon>\n\n\t\t<div [class]=\"buttonClass()\">\n\t\t\t<st-button [circle]=\"true\" size=\"sm\" [shadow]=\"true\" variant=\"secondary\" (click)=\"handleTogglePlay()\">\n\t\t\t\t<div class=\"relative h-full w-full\">\n\t\t\t\t\t<div [class]=\"waveBarsVar\">\n\t\t\t\t\t\t<div class=\"flex h-full w-full items-center justify-between\">\n\t\t\t\t\t\t\t@for (bar of waveBars; track $index) {\n\t\t\t\t\t\t\t\t<div [class]=\"bar.class\" [style.--i]=\"bar.delay\"></div>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div [class]=\"iconClassVar\">\n\t\t\t\t\t\t<st-icon [icon]=\"iconVar\"></st-icon>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</st-button>\n\t\t</div>\n\t</div>\n} @else {\n\t<st-button\n\t\t[variant]=\"variant()\"\n\t\t[ghost]=\"ghost()\"\n\t\t[outline]=\"outline()\"\n\t\t[circle]=\"circle()\"\n\t\t[square]=\"square()\"\n\t\t[glass]=\"glass()\"\n\t\t[size]=\"size()\"\n\t\t[shadow]=\"shadow()\"\n\t\t(click)=\"handleTogglePlay()\"\n\t\t(mouseenter)=\"handleHover(true)\"\n\t\t(mouseleave)=\"handleHover(false)\"\n\t>\n\t\t<div class=\"relative h-full w-full\">\n\t\t\t<div [class]=\"waveBarsVar\">\n\t\t\t\t<div class=\"flex h-full w-full items-center justify-between\">\n\t\t\t\t\t@for (bar of waveBars; track $index) {\n\t\t\t\t\t\t<div [class]=\"bar.class\" [style.--i]=\"bar.delay\"></div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div [class]=\"iconClassVar\">\n\t\t\t\t<st-icon [icon]=\"iconVar\"></st-icon>\n\t\t\t</div>\n\t\t</div>\n\t</st-button>\n}\n", styles: [""], dependencies: [{ kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: ButtonPlayComponent, decorators: [{
type: Component,
args: [{ selector: 'st-button-play', imports: [IconComponent, CommonModule, ButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let waveBarsVar = waveBarsClass();\n@let iconClassVar = iconClass();\n@let iconVar = icon();\n\n@if (!asButton()) {\n\t<div\n\t\tclass=\"from-primary to-primary-gradient relative grid h-10 min-h-10 w-10 min-w-10 place-content-center rounded-sm bg-linear-to-br text-lg text-white\"\n\t\t(mouseenter)=\"handleHover(true)\"\n\t\t(mouseleave)=\"handleHover(false)\"\n\t>\n\t\t<st-icon [icon]=\"iconMusicNote\"></st-icon>\n\n\t\t<div [class]=\"buttonClass()\">\n\t\t\t<st-button [circle]=\"true\" size=\"sm\" [shadow]=\"true\" variant=\"secondary\" (click)=\"handleTogglePlay()\">\n\t\t\t\t<div class=\"relative h-full w-full\">\n\t\t\t\t\t<div [class]=\"waveBarsVar\">\n\t\t\t\t\t\t<div class=\"flex h-full w-full items-center justify-between\">\n\t\t\t\t\t\t\t@for (bar of waveBars; track $index) {\n\t\t\t\t\t\t\t\t<div [class]=\"bar.class\" [style.--i]=\"bar.delay\"></div>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div [class]=\"iconClassVar\">\n\t\t\t\t\t\t<st-icon [icon]=\"iconVar\"></st-icon>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</st-button>\n\t\t</div>\n\t</div>\n} @else {\n\t<st-button\n\t\t[variant]=\"variant()\"\n\t\t[ghost]=\"ghost()\"\n\t\t[outline]=\"outline()\"\n\t\t[circle]=\"circle()\"\n\t\t[square]=\"square()\"\n\t\t[glass]=\"glass()\"\n\t\t[size]=\"size()\"\n\t\t[shadow]=\"shadow()\"\n\t\t(click)=\"handleTogglePlay()\"\n\t\t(mouseenter)=\"handleHover(true)\"\n\t\t(mouseleave)=\"handleHover(false)\"\n\t>\n\t\t<div class=\"relative h-full w-full\">\n\t\t\t<div [class]=\"waveBarsVar\">\n\t\t\t\t<div class=\"flex h-full w-full items-center justify-between\">\n\t\t\t\t\t@for (bar of waveBars; track $index) {\n\t\t\t\t\t\t<div [class]=\"bar.class\" [style.--i]=\"bar.delay\"></div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div [class]=\"iconClassVar\">\n\t\t\t\t<st-icon [icon]=\"iconVar\"></st-icon>\n\t\t\t</div>\n\t\t</div>\n\t</st-button>\n}\n" }]
}] });
class AudioTimelineComponent {
timeline = viewChild('timeline');
audioService = inject(AudioPlayerService);
isDragging = signal(false);
isHovered = signal(false);
timelineClass = computed(() => cn('relative h-1 w-full cursor-pointer rounded-full bg-neutral ', { 'h-[6px]': this.isHovered() || this.isDragging() }));
timelineCircleClass = computed(() => cn('transition-opacity duration-200 absolute top-1/2 h-3 w-3 -translate-y-1/2 transform cursor-grab rounded-full bg-tertiary shadow-main', this.isHovered() || this.isDragging() ? 'opacity-100' : 'opacity-0'));
// Handle timeline hover
handleTimelineHover(isHovered) {
this.isHovered.set(isHovered);
}
// Start dragging
startDrag(event) {
event.preventDefault(); // Prevent default behavior
event.stopPropagation(); // Stop event propagation
this.isDragging.set(true);
this.updateProgress(event);
}
// Handle dragging
onDrag(event) {
if (this.isDragging()) {
this.updateProgress(event);
}
}
// Stop dragging
stopDrag() {
this.isDragging.set(false);
}
// Update progress based on mouse/touch position
updateProgress(event) {
const timelineRect = this.timeline()?.nativeElement.getBoundingClientRect();
if (!timelineRect)
return;
const clientX = this.getClientX(event);
const newProgress = Math.max(0, Math.min(1, (clientX - timelineRect.left) / timelineRect.width));
this.audioService.seekTo(newProgress * this.audioService.duration());
}
getClientX(event) {
return event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
}
seek(event) {
if (!this.isDragging()) {
this.updateProgress(event);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AudioTimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: AudioTimelineComponent, isStandalone: true, selector: "st-timeline", host: { listeners: { "document:mousemove": "onDrag($event)", "document:touchmove": "onDrag($event)", "document:mouseup": "stopDrag()", "document:touchend": "stopDrag()" } }, viewQueries: [{ propertyName: "timeline", first: true, predicate: ["timeline"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n\n<!-- Progress Bar -->\n<div class=\"grid w-full grid-cols-[1fr_5fr_1fr] items-center gap-4\">\n\t<!-- Current Time Display -->\n\t<div class=\"text-tertiary inline-flex min-h-5 items-center justify-end align-top select-none\">\n\t\t@if (!audioService.isInitializing()) {\n\t\t\t@if (audioService.isBuffering()) {\n\t\t\t\t<span class=\"loading loading-ring loading-xs\"></span>\n\t\t\t} @else {\n\t\t\t\t<span typography tyVariant=\"body-xxs\">{{ audioService.currentTime() | time }}</span>\n\t\t\t}\n\t\t}\n\t</div>\n\n\t<div [class]=\"timelineClass()\" (click)=\"seek($event)\" #timeline (mouseenter)=\"handleTimelineHover(true)\" (mouseleave)=\"handleTimelineHover(false)\">\n\t\t<!-- Progress Bar Background -->\n\t\t<div class=\"bg-primary absolute h-full rounded-full\" [style.width]=\"(audioService.currentTime() / audioService.duration()) * 100 + '%'\"></div>\n\n\t\t<!-- Draggable Circle -->\n\t\t<div\n\t\t\t[style.left]=\"\n\t\t\t\t'calc(' +\n\t\t\t\t(audioService.currentTime() / audioService.duration()) * 100 +\n\t\t\t\t'% - ' +\n\t\t\t\t(audioService.currentTime() / audioService.duration() === 1 ? '6px' : '3px') +\n\t\t\t\t')'\n\t\t\t\"\n\t\t\t[class]=\"timelineCircleClass()\"\n\t\t\t#circle\n\t\t\t(mousedown)=\"startDrag($event)\"\n\t\t\t(touchstart)=\"startDrag($event)\"\n\t\t></div>\n\t</div>\n\n\t<!-- Total Time Display -->\n\t<div class=\"text-tertiary inline-flex min-h-5 items-center justify-start align-top select-none\">\n\t\t@if (audioService.isInitializing()) {\n\t\t\t<span class=\"loading loading-ring loading-xs\"></span>\n\t\t} @else {\n\t\t\t<span typography tyVariant=\"body-xxs\">{{ audioService.duration() | time }}</span>\n\t\t}\n\t</div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TimePipe, name: "time" }, { kind: "directive", type: TypographyDirective, selector: "[typography]", inputs: ["tyVariant", "tyColor", "tyFontWeight", "tyTextOverflow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AudioTimelineComponent, decorators: [{
type: Component,
args: [{ selector: 'st-timeline', imports: [CommonModule, TimePipe, TypographyDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n\n<!-- Progress Bar -->\n<div class=\"grid w-full grid-cols-[1fr_5fr_1fr] items-center gap-4\">\n\t<!-- Current Time Display -->\n\t<div class=\"text-tertiary inline-flex min-h-5 items-center justify-end align-top select-none\">\n\t\t@if (!audioService.isInitializing()) {\n\t\t\t@if (audioService.isBuffering()) {\n\t\t\t\t<span class=\"loading loading-ring loading-xs\"></span>\n\t\t\t} @else {\n\t\t\t\t<span typography tyVariant=\"body-xxs\">{{ audioService.currentTime() | time }}</span>\n\t\t\t}\n\t\t}\n\t</div>\n\n\t<div [class]=\"timelineClass()\" (click)=\"seek($event)\" #timeline (mouseenter)=\"handleTimelineHover(true)\" (mouseleave)=\"handleTimelineHover(false)\">\n\t\t<!-- Progress Bar Background -->\n\t\t<div class=\"bg-primary absolute h-full rounded-full\" [style.width]=\"(audioService.currentTime() / audioService.duration()) * 100 + '%'\"></div>\n\n\t\t<!-- Draggable Circle -->\n\t\t<div\n\t\t\t[style.left]=\"\n\t\t\t\t'calc(' +\n\t\t\t\t(audioService.currentTime() / audioService.duration()) * 100 +\n\t\t\t\t'% - ' +\n\t\t\t\t(audioService.currentTime() / audioService.duration() === 1 ? '6px' : '3px') +\n\t\t\t\t')'\n\t\t\t\"\n\t\t\t[class]=\"timelineCircleClass()\"\n\t\t\t#circle\n\t\t\t(mousedown)=\"startDrag($event)\"\n\t\t\t(touchstart)=\"startDrag($event)\"\n\t\t></div>\n\t</div>\n\n\t<!-- Total Time Display -->\n\t<div class=\"text-tertiary inline-flex min-h-5 items-center justify-start align-top select-none\">\n\t\t@if (audioService.isInitializing()) {\n\t\t\t<span class=\"loading loading-ring loading-xs\"></span>\n\t\t} @else {\n\t\t\t<span typography tyVariant=\"body-xxs\">{{ audioService.duration() | time }}</span>\n\t\t}\n\t</div>\n</div>\n" }]
}], propDecorators: { onDrag: [{
type: HostListener,
args: ['document:mousemove', ['$event']]
}, {
type: HostListener,
args: ['document:touchmove', ['$event']]
}], stopDrag: [{
type: HostListener,
args: ['document:mouseup']
}, {
type: HostListener,
args: ['document:touchend']
}] } });
class TrackInfoComponent {
currentTrack = input();
iconMusicNote = heroMusicalNoteMini;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: TrackInfoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.0", type: TrackInfoComponent, isStandalone: true, selector: "st-track-info", inputs: { currentTrack: { classPropertyName: "currentTrack", publicName: "currentTrack", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"relative flex max-w-80 items-center gap-4 overflow-x-hidden whitespace-nowrap\">\n\t<div class=\"flex items-center gap-3\">\n\t\t<!-- Album/Track Icon -->\n\t\t<div class=\"from-primary to-primary-gradient grid h-12 min-h-12 w-12 min-w-12 place-content-center rounded-sm bg-linear-to-br text-white\">\n\t\t\t<st-icon [icon]=\"iconMusicNote\" size=\"md\"></st-icon>\n\t\t</div>\n\n\t\t<!-- Track Info -->\n\t\t@let currentTrackVar = this.currentTrack();\n\t\t<div class=\"min-w-0 flex-1\">\n\t\t\t<p typography tyVariant=\"body\" tyFontWeight=\"medium\" tyColor=\"base\">\n\t\t\t\t{{ currentTrackVar?.title ?? 'sdk.audioPlayer.trackInfo.title' | translate }}\n\t\t\t</p>\n\t\t\t<p typography tyVariant=\"body-sm\" tyFontWeight=\"normal\" tyColor=\"base\">\n\t\t\t\t{{ currentTrackVar?.description ?? 'sdk.audioPlayer.trackInfo.description' | translate }}\n\t\t\t</p>\n\t\t</div>\n\t</div>\n\t<div class=\"from-base-200 pointer-events-none absolute inset-y-0 right-0 w-12 bg-linear-to-l to-transparent\"></div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TypographyDirective, selector: "[typography]", inputs: ["tyVariant", "tyColor", "tyFontWeight", "tyTextOverflow"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: TrackInfoComponent, decorators: [{
type: Component,
args: [{ selector: 'st-track-info', imports: [IconComponent, CommonModule, TypographyDirective, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"relative flex max-w-80 items-center gap-4 overflow-x-hidden whitespace-nowrap\">\n\t<div class=\"flex items-center gap-3\">\n\t\t<!-- Album/Track Icon -->\n\t\t<div class=\"from-primary to-primary-gradient grid h-12 min-h-12 w-12 min-w-12 place-content-center rounded-sm bg-linear-to-br text-white\">\n\t\t\t<st-icon [icon]=\"iconMusicNote\" size=\"md\"></st-icon>\n\t\t</div>\n\n\t\t<!-- Track Info -->\n\t\t@let currentTrackVar = this.currentTrack();\n\t\t<div class=\"min-w-0 flex-1\">\n\t\t\t<p typography tyVariant=\"body\" tyFontWeight=\"medium\" tyColor=\"base\">\n\t\t\t\t{{ currentTrackVar?.title ?? 'sdk.audioPlayer.trackInfo.title' | translate }}\n\t\t\t</p>\n\t\t\t<p typography tyVariant=\"body-sm\" tyFontWeight=\"normal\" tyColor=\"base\">\n\t\t\t\t{{ currentTrackVar?.description ?? 'sdk.audioPlayer.trackInfo.description' | translate }}\n\t\t\t</p>\n\t\t</div>\n\t</div>\n\t<div class=\"from-base-200 pointer-events-none absolute inset-y-0 right-0 w-12 bg-linear-to-l to-transparent\"></div>\n</div>\n" }]
}] });
class VolumeControlComponent {
volumeTimeline = viewChild('volumeTimeline');
audioService = inject(AudioPlayerService);
isVolumeDragging = signal(false);
isVolumeHovered = signal(false);
iconVolumeUp = matVolumeUpOutline;
iconVolumeDown = matVolumeDownOutline;
iconVolumeMute = matVolumeMuteOutline;
volumeTimelineClass = computed(() => {
return cn('relative h-1 w-20 cursor-pointer rounded-full bg-neutral', {
'h-[6px]': this.isVolumeHovered() || this.isVolumeDragging(),
});
});
volumeTimelineCircleClass = computed(() => {
return cn('absolute top-1/2 h-3 w-3 -translate-y-1/2 transform cursor-grab rounded-full bg-tertiary shadow-main transition-opacity duration-200', this.isVolumeHovered() || this.isVolumeDragging() ? 'opacity-100' : 'opacity-0');
});
// Handle volume hover
handleVolumeHover(isHovered) {
this.isVolumeHovered.set(isHovered);
}
// Start volume drag
startVolumeDrag(event) {
event.preventDefault(); // Prevent default behavior
event.stopPropagation(); // Prevent event propagation
this.isVolumeDragging.set(true);
this.updateVolume(event);
}
// Handle volume drag
onVolumeDrag(event) {
if (this.isVolumeDragging()) {
this.updateVolume(event);
}
}
// Stop volume drag
stopVolumeDrag() {
this.isVolumeDragging.set(false);
}
getClientX(event) {
return event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
}
updateVolume(event) {
const volumeTimelineRect = this.volumeTimeline()?.nativeElement.getBoundingClientRect();
if (!volumeTimelineRect)
return;
const clientX = this.getClientX(event);
const newVolume = Math.max(0, Math.min(1, (clientX - volumeTimelineRect.left) / volumeTimelineRect.width));
this.audioService.setVolume(newVolume);
}
// Seek volume on click
seekVolume(event) {
if (!this.isVolumeDragging()) {
this.updateVolume(event);
}
}
toggleMute() {
this.audioService.setMuted(!this.audioService.isMuted());
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: VolumeControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: VolumeControlComponent, isStandalone: true, selector: "st-volume-control", host: { listeners: { "document:mousemove": "onVolumeDrag($event)", "document:touchmove": "onVolumeDrag($event)", "document:mouseup": "stopVolumeDrag()", "document:touchend": "stopVolumeDrag()" } }, viewQueries: [{ propertyName: "volumeTimeline", first: true, predicate: ["volumeTimeline"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n\n<!-- Volume Control -->\n<div class=\"flex items-center justify-end gap-2\">\n\t<!-- Mute/Unmute Button -->\n\t<st-button [ghost]=\"true\" size=\"sm\" [circle]=\"true\" (click)=\"toggleMute()\">\n\t\t@if (audioService.volume() === 0) {\n\t\t\t<st-icon [icon]=\"iconVolumeMute\" size=\"sm\"></st-icon>\n\t\t} @else if (audioService.volume() > 0.5) {\n\t\t\t<st-icon [icon]=\"iconVolumeUp\" size=\"sm\"></st-icon>\n\t\t} @else {\n\t\t\t<st-icon [icon]=\"iconVolumeDown\" size=\"sm\"></st-icon>\n\t\t}\n\t</st-button>\n\n\t<!-- Custom Volume Progress Bar -->\n\t<div\n\t\t[class]=\"volumeTimelineClass()\"\n\t\t(click)=\"seekVolume($event)\"\n\t\t#volumeTimeline\n\t\t(mouseenter)=\"handleVolumeHover(true)\"\n\t\t(mouseleave)=\"handleVolumeHover(false)\"\n\t>\n\t\t<!-- Volume Progress Bar Background -->\n\t\t<div class=\"bg-primary absolute h-full rounded-full\" [style.width]=\"audioService.volume() * 100 + '%'\"></div>\n\n\t\t<!-- Draggable Volume Circle -->\n\t\t<div\n\t\t\t[style.left]=\"'calc(' + audioService.volume() * 100 + '% - ' + (audioService.volume() === 1 ? '6px' : '3px') + ')'\"\n\t\t\t[class]=\"volumeTimelineCircleClass()\"\n\t\t\t#volumeCircle\n\t\t\t(mousedown)=\"startVolumeDrag($event)\"\n\t\t\t(touchstart)=\"startVolumeDrag($event)\"\n\t\t></div>\n\t</div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: VolumeControlComponent, decorators: [{
type: Component,
args: [{ selector: 'st-volume-control', imports: [IconComponent, CommonModule, ButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- eslint-disable @angular-eslint/template/interactive-supports-focus -->\n<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->\n\n<!-- Volume Control -->\n<div class=\"flex items-center justify-end gap-2\">\n\t<!-- Mute/Unmute Button -->\n\t<st-button [ghost]=\"true\" size=\"sm\" [circle]=\"true\" (click)=\"toggleMute()\">\n\t\t@if (audioService.volume() === 0) {\n\t\t\t<st-icon [icon]=\"iconVolumeMute\" size=\"sm\"></st-icon>\n\t\t} @else if (audioService.volume() > 0.5) {\n\t\t\t<st-icon [icon]=\"iconVolumeUp\" size=\"sm\"></st-icon>\n\t\t} @else {\n\t\t\t<st-icon [icon]=\"iconVolumeDown\" size=\"sm\"></st-icon>\n\t\t}\n\t</st-button>\n\n\t<!-- Custom Volume Progress Bar -->\n\t<div\n\t\t[class]=\"volumeTimelineClass()\"\n\t\t(click)=\"seekVolume($event)\"\n\t\t#volumeTimeline\n\t\t(mouseenter)=\"handleVolumeHover(true)\"\n\t\t(mouseleave)=\"handleVolumeHover(false)\"\n\t>\n\t\t<!-- Volume Progress Bar Background -->\n\t\t<div class=\"bg-primary absolute h-full rounded-full\" [style.width]=\"audioService.volume() * 100 + '%'\"></div>\n\n\t\t<!-- Draggable Volume Circle -->\n\t\t<div\n\t\t\t[style.left]=\"'calc(' + audioService.volume() * 100 + '% - ' + (audioService.volume() === 1 ? '6px' : '3px') + ')'\"\n\t\t\t[class]=\"volumeTimelineCircleClass()\"\n\t\t\t#volumeCircle\n\t\t\t(mousedown)=\"startVolumeDrag($event)\"\n\t\t\t(touchstart)=\"startVolumeDrag($event)\"\n\t\t></div>\n\t</div>\n</div>\n" }]
}], propDecorators: { onVolumeDrag: [{
type: HostListener,
args: ['document:mousemove', ['$event']]
}, {
type: HostListener,
args: ['document:touchmove', ['$event']]
}], stopVolumeDrag: [{
type: HostListener,
args: ['document:mouseup']
}, {
type: HostListener,
args: ['document:touchend']
}] } });
class AudioPlayerComponent {
audioPlayerService = inject(AudioPlayerService);
variant = input('default');
shadow = input(false);
opened = model(false);
tracks = input([]);
showTrackList = input(false);
currentTrackIndex = model(-1);
currentTrack = computed(() => {
const tracks = this.tracks();
return this.currentTrackIndex() >= 0 && tracks ? tracks[this.currentTrackIndex()] : undefined;
});
iconSkipPrevious = matSkipPrevious;
iconSkipNext = matSkipNext;
iconPlay = matPlayArrow;
iconPause = matPause;
iconMusicNote = heroMusicalNoteMini;
offScreen = signal(false);
playerClass = computed(() => cn('grid w-full grid-cols-[minmax(0,1fr)_minmax(0,2fr)_minmax(0,1fr)] items-center gap-4 relative translate-y-0', {
'shadow-main': this.shadow(),
}, {
'fixed bottom-0 left-0 bg-base-200 p-4 z-50 border-t border-neutral transform transition-transform duration-300': this.variant() === 'fixed',
}, {
'animate-fade-in-up animate-steps-modern animate-duration-300': this.opened() && this.variant() === 'fixed',
}, {
'animate-fade-out-down animate-steps-modern animate-duration-300': !this.opened() && this.variant() === 'fixed',
}, {
'invisible pointer-events-none -z-10 fixed bottom-5': this.offScreen() && this.variant() === 'fixed',
}));
constructor() {
// Sync tracks with service
effect(() => {
this.audioPlayerService.setTracks(this.tracks());
});
// One-way sync from service to component
effect(() => {
this.currentTrackIndex.set(this.audioPlayerService.currentTrackIndex());
});
}
entering = signal(false);
leaving = signal(false);
handleAnimationEnd(event) {
if (this.variant() !== 'fixed')
return;
if (event.animationName === 'fade-out-down' && !this.opened()) {
this.offScreen.set(true);
this.leaving.set(false);
}
if (event.animationName === 'fade-in-up' && this.opened()) {
this.entering.set(false);
}
}
handleAnimationStart(event) {
if (this.variant() !== 'fixed')
return;
if (event.animationName === 'fade-in-up' && this.opened()) {
this.offScreen.set(false);
this.entering.set(true);
}
if (event.animationName === 'fade-out-down' && !this.opened()) {
this.leaving.set(true);
}
}
// Simplify methods to call service
async playTrack(index) {
await this.audioPlayerService.playTrack(index);
}
async togglePlay() {
await this.audioPlayerService.togglePlay();
}
async previous() {
await this.audioPlayerService.previous();
}
async next() {
await this.audioPlayerService.next();
}
handleOpen() {
this.opened.set(true);
}
handleClose() {
this.opened.set(false);
}
ngOnDestroy() {
this.audioPlayerService.cleanup();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AudioPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: AudioPlayerComponent, isStandalone: true, selector: "st-audio-player", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, shadow: { classPropertyName: "shadow", publicName: "shadow", isSignal: true, isRequired: false, transformFunction: null }, opened: { classPropertyName: "opened", publicName: "opened", isSignal: true, isRequired: false, transformFunction: null }, tracks: { classPropertyName: "tracks", publicName: "tracks", isSignal: true, isRequired: false, transformFunction: null }, showTrackList: { classPropertyName: "showTrackList", publicName: "showTrackList", isSignal: true, isRequired: false, transformFunction: null }, currentTrackIndex: { classPropertyName: "currentTrackIndex", publicName: "currentTrackIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "openedChange", currentTrackIndex: "currentTrackIndexChange" }, ngImport: i0, template: "<div class=\"flex w-full flex-col gap-4\">\n\t<!-- Track List -->\n\t@if (showTrackList()) {\n\t\t<div class=\"rounded-box border-neutral shadow-main mb-4 border\">\n\t\t\t<div class=\"p-4\">\n\t\t\t\t<h4 typography tyVariant=\"h4\">{{ 'sdk.audioPlayer.trackListTitle' | translate }}</h4>\n\t\t\t\t<div class=\"max-h-[300px] space-y-2 overflow-y-auto\">\n\t\t\t\t\t@for (track of tracks(); track $index; let i = $index) {\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"rounded-field hover:bg-base-300 flex cursor-pointer items-center gap-2 p-2\"\n\t\t\t\t\t\t\t[class.bg-base-300]=\"audioPlayerService.currentTrackIndex() === i\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<st-button-play [trackIndex]=\"i\" (click)=\"handleOpen()\"> </st-button-play>\n\t\t\t\t\t\t\t<div class=\"flex-1\">\n\t\t\t\t\t\t\t\t<div typography tyVariant=\"body\">{{ track.title }}</div>\n\t\t\t\t\t\t\t\t<div typography tyVariant=\"body-sm\">{{ track.description }}</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t}\n\n\t<div [class]=\"playerClass()\" (animationend)=\"handleAnimationEnd($event)\" (animationstart)=\"handleAnimationStart($event)\">\n\t\t<!-- close button -->\n\t\t@if (variant() === 'fixed') {\n\t\t\t<div\n\t\t\t\tclass=\"group/close btn btn-ghost btn-square btn-primary btn-xs absolute top-1 right-1 grid place-content-center gap-0 hover:cursor-pointer\"\n\t\t\t\ttabindex=\"0\"\n\t\t\t\t(click)=\"handleClose()\"\n\t\t\t\t(keydown.enter)=\"handleClose()\"\n\t\t\t\taria-label=\"Close player\"\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"bg-primary group-hover/close:bg-primary-content group-active/close:bg-primary-content group-focus-within/close:bg-primary-content relative h-0.5 w-4 translate-y-0.5 rounded-full transition-transform duration-300 ease-in-out group-focus-within/close:rotate-45 group-hover/close:rotate-45 group-active/close:rotate-45\"\n\t\t\t\t></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"bg-primary group-hover/close:bg-primary-content group-active/close:bg-primary-content group-focus-within/close:bg-primary-content h-0.5 w-4 rounded-full transition-transform duration-300 ease-in-out group-focus-within/close:-rotate-45 group-hover/close:-rotate-45 group-active/close:-rotate-45\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t}\n\n\t\t<!-- track info -->\n\t\t<div class=\"min-w-0\">\n\t\t\t<!-- Added min-w-0 to prevent overflow -->\n\t\t\t<st-track-info [currentTrack]=\"currentTrack()\"></st-track-info>\n\t\t</div>\n\n\t\t<div class=\"min-w-0\">\n\t\t\t<!-- Center section -->\n\t\t\t<!-- Your controls content -->\n\t\t\t<div class=\"grid items-center\">\n\t\t\t\t<!-- Controls -->\n\t\t\t\t<div class=\"flex items-center justify-center gap-2\">\n\t\t\t\t\t<st-button (click)=\"previous()\" [ghost]=\"true\" [circle]=\"true\" size=\"sm\">\n\t\t\t\t\t\t<st-icon [icon]=\"iconSkipPrevious\" size=\"sm\"></st-icon>\n\t\t\t\t\t</st-button>\n\n\t\t\t\t\t<st-button (click)=\"togglePlay()\" [circle]=\"true\" size=\"sm\" [shadow]=\"true\">\n\t\t\t\t\t\t<st-icon [icon]=\"audioPlayerService.isPlaying() ? iconPause : iconPlay\" size=\"sm\"></st-icon>\n\t\t\t\t\t</st-button>\n\n\t\t\t\t\t<st-button (click)=\"next()\" [ghost]=\"true\" [circle]=\"true\" size=\"sm\">\n\t\t\t\t\t\t<st-icon [icon]=\"iconSkipNext\" size=\"sm\"></st-icon>\n\t\t\t\t\t</st-button>\n\t\t\t\t</div>\n\n\t\t\t\t<!-- Progress Bar -->\n\t\t\t\t<st-timeline></st-timeline>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Volume Control -->\n\t\t<div class=\"min-w-0 justify-self-end\">\n\t\t\t<!-- Right-aligned -->\n\t\t\t<st-volume-control></st-volume-control>\n\t\t</div>\n\t</div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ButtonComponent, selector: "st-button", inputs: ["variant", "ghost", "outline", "link", "soft", "dash", "wide", "circle", "square", "glass", "block", "loader", "size", "shadow", "focusable", "icon", "iconPosition", "type", "disabled"] }, { kind: "directive", type: TypographyDirective, selector: "[typography]", inputs: ["tyVariant", "tyColor", "tyFontWeight", "tyTextOverflow"] }, { kind: "component", type: VolumeControlComponent, selector: "st-volume-control" }, { kind: "component", type: TrackInfoComponent, selector: "st-track-info", inputs: ["currentTrack"] }, { kind: "component", type: AudioTimelineComponent, selector: "st-timeline" }, { kind: "component", type: ButtonPlayComponent, selector: "st-button-play", inputs: ["variant", "ghost", "outline", "circle", "square", "glass", "size", "shadow", "asButton", "trackIndex"], outputs: ["togglePlay"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: IconComponent, selector: "st-icon", inputs: ["color", "size", "icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AudioPlayerComponent, decorators: [{
type: Component,
args: [{ selector: 'st-audio-player', imports: [
CommonModule,
ButtonComponent,
TypographyDirective,
VolumeControlComponent,
TrackInfoComponent,
AudioTimelineComponent,
ButtonPlayComponent,
TranslatePipe,
IconComponent,
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"flex w-full flex-col gap-4\">\n\t<!-- Track List -->\n\t@if (showTrackList()) {\n\t\t<div class=\"rounded-box border-neutral shadow-main mb-4 border\">\n\t\t\t<div class=\"p-4\">\n\t\t\t\t<h4 typography tyVariant=\"h4\">{{ 'sdk.audioPlayer.trackListTitle' | translate }}</h4>\n\t\t\t\t<div class=\"max-h-[300px] space-y-2 overflow-y-auto\">\n\t\t\t\t\t@for (track of tracks(); track $index; let i = $index) {\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclass=\"rounded-field hover:bg-base-300 flex cursor-pointer items-center gap-2 p-2\"\n\t\t\t\t\t\t\t[class.bg-base-300]=\"audioPlayerService.currentTrackIndex() === i\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<st-button-play [trackIndex]=\"i\" (click)=\"handleOpen()\"> </st-button-play>\n\t\t\t\t\t\t\t<div class=\"flex-1\">\n\t\t\t\t\t\t\t\t<div typography tyVariant=\"body\">{{ track.title }}</div>\n\t\t\t\t\t\t\t\t<div typography tyVariant=\"body-sm\">{{ track.description }}</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t}\n\n\t<div [class]=\"playerClass()\" (animationend)=\"handleAnimationEnd($event)\" (animationstart)=\"handleAnimationStart($event)\">\n\t\t<!-- close button -->\n\t\t@if (variant() === 'fixed') {\n\t\t\t<div\n\t\t\t\tclass=\"group/close btn btn-ghost btn-square btn-primary btn-xs absolute top-1 right-1 grid place-content-center gap-0 hover:cursor-pointer\"\n\t\t\t\ttabindex=\"0\"\n\t\t\t\t(click)=\"handleClose()\"\n\t\t\t\t(keydown.enter)=\"handleClose()\"\n\t\t\t\taria-label=\"Close player\"\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"bg-primary group-hover/close:bg-primary-content group-active/close:bg-primary-content group-focus-within/close:bg-primary-content relative h-0.5 w-4 translate-y-0.5 rounded-full transition-transform duration-300 ease-in-out group-focus-within/close:rotate-45 group-hover/close:rotate-45 group-active/close:rotate-45\"\n\t\t\t\t></div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"bg-primary group-hover/close:bg-primary-content group-active/close:bg-primary-content group-focus-within/close:bg-primary-content h-0.5 w-4 rounded-full transition-transform duration-300 ease-in-out group-focus-within/close:-rotate-45 group-hover/close:-rotate-45 group-active/close:-rotate-45\"\n\t\t\t\t></div>\n\t\t\t</div>\n\t\t}\n\n\t\t<!-- track info -->\n\t\t<div class=\"min-w-0\">\n\t\t\t<!-- Added min-w-0 to prevent overflow -->\n\t\t\t<st-track-info [currentTrack]=\"currentTrack()\"></st-track-info>\n\t\t</div>\n\n\t\t<div class=\"min-w-0\">\n\t\t\t<!-- Center section -->\n\t\t\t<!-- Your controls content -->\n\t\t\t<div class=\"grid items-center\">\n\t\t\t\t<!-- Controls -->\n\t\t\t\t<div class=\"flex items-center justify-center gap-2\">\n\t\t\t\t\t<st-button (click)=\"previous()\" [ghost]=\"true\" [circle]=\"true\" size=\"sm\">\n\t\t\t\t\t\t<st-icon [icon]=\"iconSkipPrevious\" size=\"sm\"></st-icon>\n\t\t\t\t\t</st-button>\n\n\t\t\t\t\t<st-button (click)=\"togglePlay()\" [circle]=\"true\" size=\"sm\" [shadow]=\"true\">\n\t\t\t\t\t\t<st-icon [icon]=\"audioPlayerService.isPlaying() ? iconPause : iconPlay\" size=\"sm\"></st-icon>\n\t\t\t\t\t</st-button>\n\n\t\t\t\t\t<st-button (click)=\"next()\" [ghost]=\"true\" [circle]=\"true\" size=\"sm\">\n\t\t\t\t\t\t<st-icon [icon]=\"iconSkipNext\" size=\"sm\"></st-icon>\n\t\t\t\t\t</st-button>\n\t\t\t\t</div>\n\n\t\t\t\t<!-- Progress Bar -->\n\t\t\t\t<st-timeline></st-timeline>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Volume Control -->\n\t\t<div class=\"min-w-0 justify-self-end\">\n\t\t\t<!-- Right-aligned -->\n\t\t\t<st-volume-control></st-volume-control>\n\t\t</div>\n\t</div>\n</div>\n" }]
}], ctorParameters: () => [] });
/**
* Generated bundle index. Do not edit.
*/
export { AudioPlayerComponent, AudioPlayerService, ButtonPlayComponent };
//# sourceMappingURL=sixbell-telco-sdk-components-audio-player.mjs.map