From 5e9e53a02a56e6eda3a8fbbc780cec7e959275e9 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 20 Aug 2023 20:59:40 +0200 Subject: [PATCH] feat(radio): add audio player for radio playback --- .../audio-player/audio-player.component.scss | 112 +++++++++++++++ .../audio-player/audio-player.component.ts | 134 ++++++++++++++++++ src/app/player/player.module.ts | 2 + 3 files changed, 248 insertions(+) create mode 100644 src/app/player/components/audio-player/audio-player.component.scss create mode 100644 src/app/player/components/audio-player/audio-player.component.ts diff --git a/src/app/player/components/audio-player/audio-player.component.scss b/src/app/player/components/audio-player/audio-player.component.scss new file mode 100644 index 000000000..4a7e85b07 --- /dev/null +++ b/src/app/player/components/audio-player/audio-player.component.scss @@ -0,0 +1,112 @@ +:host { + display: flex; + justify-content: center; + margin: 20px 0; +} + +#audio-player { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; +} + +.radio-logo { + border-radius: 14px; + height: 300px; + width: 300px; + display: flex; + align-items: center; + justify-content: center; + + img { + max-width: 300px; + max-height: 300px; + } +} + +.volume-panel { + display: flex; + align-self: center; + align-items: center; + + input { + flex: 1; + } +} + +.controls { + height: 100px; + align-items: center; + display: flex; + gap: 8px; + justify-content: center; +} + + +.playing { + width: 36px; + height: 36px; + border-radius: .3rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + padding: .5rem; + box-sizing: border-box; + } + + .playing__bar { + display: inline-block; + background: white; + width: 30%; + height: 100%; + animation: up-and-down 1.3s ease infinite alternate; + } + + .playing__bar__stopped { + display: inline-block; + background: white; + width: 30%; + height: 100%; + } + + .playing__bar1 { + height: 60%; + } + + .playing__bar2 { + height: 30%; + animation-delay: -2.2s; + } + + .playing__bar3 { + height: 75%; + animation-delay: -3.7s; + } + + @keyframes up-and-down { + 10% { + height: 30%; + } + + 30% { + height: 100%; + } + + 60% { + height: 50%; + } + + 80% { + height: 75%; + } + + 100% { + height: 60%; + } +} + +.icon-button-large { + transform: scale(2); + margin: 0 20px; +} diff --git a/src/app/player/components/audio-player/audio-player.component.ts b/src/app/player/components/audio-player/audio-player.component.ts new file mode 100644 index 000000000..5d380ccc5 --- /dev/null +++ b/src/app/player/components/audio-player/audio-player.component.ts @@ -0,0 +1,134 @@ +import {NgClass, NgIf, NgOptimizedImage} from '@angular/common'; +import { + Component, + ElementRef, + Input, + OnChanges, + SimpleChanges, + ViewChild, +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatSliderModule } from '@angular/material/slider'; +import { Store } from '@ngrx/store'; +import { setAdjacentChannelAsActive } from '../../../state/actions'; + +@Component({ + selector: 'app-audio-player', + standalone: true, + template: ` +
+ + +
+ + + + + +
+
+
+ + + +
+
+ + + +
+ + + + +
+
+ `, + styleUrls: ['./audio-player.component.scss'], + imports: [ + MatSliderModule, + MatIconModule, + MatButtonModule, + NgIf, + NgClass, + FormsModule, + NgOptimizedImage, + ], +}) +export class AudioPlayerComponent implements OnChanges { + @Input() icon: string; + @Input({ required: true }) url: string; + + playState: 'play' | 'paused' = 'paused'; + + fallbackVolume: number; + + @ViewChild('audio', { static: true }) audio!: ElementRef; + + constructor(private store: Store) {} + + ngOnChanges(changes: SimpleChanges): void { + this.audio.nativeElement.src = changes.url.currentValue; + this.audio.nativeElement.load(); + this.play(); + } + + play() { + const playPromise = this.audio.nativeElement.play(); + if (playPromise !== undefined) { + playPromise.catch((error) => { + console.log(error); + }); + } + this.playState = 'play'; + } + + stop() { + this.audio.nativeElement.pause(); + this.playState = 'paused'; + } + + mute() { + this.audio.nativeElement.muted = !this.audio.nativeElement.muted; + if (this.audio.nativeElement.muted) { + this.fallbackVolume = this.audio.nativeElement.volume; + this.audio.nativeElement.volume = 0; + } else this.audio.nativeElement.volume = this.fallbackVolume; + } + + switchChannel(direction: 'next' | 'previous') { + console.log(direction); + this.store.dispatch(setAdjacentChannelAsActive({ direction })); + } +} diff --git a/src/app/player/player.module.ts b/src/app/player/player.module.ts index 3a75d1662..65bfd6211 100644 --- a/src/app/player/player.module.ts +++ b/src/app/player/player.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { SharedModule } from '../shared/shared.module'; +import { AudioPlayerComponent } from './components/audio-player/audio-player.component'; import { ChannelListContainerComponent } from './components/channel-list-container/channel-list-container.component'; import { ChannelListItemComponent } from './components/channel-list-container/channel-list-item/channel-list-item.component'; import { EpgItemDescriptionComponent } from './components/epg-list/epg-item-description/epg-item-description.component'; @@ -20,6 +21,7 @@ const routes: Routes = [{ path: '', component: VideoPlayerComponent }]; @NgModule({ imports: [ + AudioPlayerComponent, CommonModule, HtmlVideoPlayerComponent, OverlayModule,