Skip to content

Commit

Permalink
feat(radio): add audio player for radio playback
Browse files Browse the repository at this point in the history
  • Loading branch information
4gray committed Aug 20, 2023
1 parent 7791686 commit 5e9e53a
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 0 deletions.
112 changes: 112 additions & 0 deletions src/app/player/components/audio-player/audio-player.component.scss
Original file line number Diff line number Diff line change
@@ -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;
}
134 changes: 134 additions & 0 deletions src/app/player/components/audio-player/audio-player.component.ts
Original file line number Diff line number Diff line change
@@ -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: `
<div id="audio-player">
<div class="radio-logo">
<img [src]="icon" alt="radio icon" />
</div>
<audio preload="metadata" autoplay #audio>
<source [src]="url" />
</audio>
<div class="controls">
<button mat-icon-button (click)="switchChannel('previous')">
<mat-icon>skip_previous</mat-icon>
</button>
<button
class="icon-button-large"
mat-icon-button
(click)="play()"
*ngIf="playState === 'paused'"
>
<mat-icon>play_arrow</mat-icon>
</button>
<button
class="icon-button-large"
mat-icon-button
(click)="stop()"
*ngIf="playState === 'play'"
>
<mat-icon>pause</mat-icon>
</button>
<button mat-icon-button (click)="switchChannel('next')">
<mat-icon>skip_next</mat-icon>
</button>
</div>
<div class="volume-panel">
<div class="playing" *ngIf="playState === 'play'">
<span class="playing__bar playing__bar1"></span>
<span class="playing__bar playing__bar2"></span>
<span class="playing__bar playing__bar3"></span>
</div>
<div class="playing" *ngIf="playState === 'paused'">
<span class="playing__bar__stopped playing__bar1"></span>
<span class="playing__bar__stopped playing__bar2"></span>
<span class="playing__bar__stopped playing__bar3"></span>
</div>
<mat-slider min="0" max="1" step="0.1" color="accent">
<input matSliderThumb [(ngModel)]="audio.volume" />
</mat-slider>
<button mat-icon-button (click)="mute()">
<mat-icon *ngIf="audio.volume > 0 && !audio.muted"
>volume_up</mat-icon
>
<mat-icon *ngIf="audio.volume === 0 || audio.muted"
>volume_off</mat-icon
>
</button>
</div>
</div>
`,
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<HTMLAudioElement>;

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 }));
}
}
2 changes: 2 additions & 0 deletions src/app/player/player.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,6 +21,7 @@ const routes: Routes = [{ path: '', component: VideoPlayerComponent }];

@NgModule({
imports: [
AudioPlayerComponent,
CommonModule,
HtmlVideoPlayerComponent,
OverlayModule,
Expand Down

1 comment on commit 5e9e53a

@vercel
Copy link

@vercel vercel bot commented on 5e9e53a Aug 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

iptvnator – ./

iptvnator.vercel.app
iptvnator-4gray.vercel.app
iptvnator-git-electron-4gray.vercel.app

Please sign in to comment.