💥 Finished up controls for so far!

Yay!
This commit is contained in:
corner 2019-09-27 19:08:36 +02:00
parent 319104dee8
commit 29d8d978aa
9 changed files with 122 additions and 29 deletions

View File

@ -31,7 +31,7 @@
<button type="button" class="btn btn-outline-secondary" (click)="toggleNav();"> <button type="button" class="btn btn-outline-secondary" (click)="toggleNav();">
<fa-icon [icon]="this.navToggled ? faChevronLeft : faChevronRight" [fixedWidth]="true"></fa-icon> <fa-icon [icon]="this.navToggled ? faChevronLeft : faChevronRight" [fixedWidth]="true"></fa-icon>
</button> </button>
<span class="navbar-brand">Route name here</span> <span class="navbar-brand">{{ router.url }}</span>
<form class="form-inline" id="navbar-search"> <form class="form-inline" id="navbar-search">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>

View File

@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { faGlobeEurope, faMusic, faCompactDisc, faUsers, faCog, faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons'; import { faGlobeEurope, faMusic, faCompactDisc, faUsers, faCog, faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
import { Router, UrlSegment } from '@angular/router';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -20,11 +21,10 @@ export class AppComponent implements OnInit {
faChevronRight = faChevronRight; faChevronRight = faChevronRight;
constructor() { constructor(public router: Router) { }
this.navToggled = false;
}
ngOnInit() { ngOnInit() {
this.navToggled = false;
} }
toggleNav() { toggleNav() {

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@ -25,7 +26,8 @@ import { ControlsComponent } from './components/controls/controls.component';
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,
HttpClientModule, HttpClientModule,
FontAwesomeModule FontAwesomeModule,
FormsModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -26,6 +26,15 @@ export class Song {
// tslint:disable-next-line: no-use-before-declare // tslint:disable-next-line: no-use-before-declare
return new Artist(await this.data.getArtist(this.artist)); return new Artist(await this.data.getArtist(this.artist));
} }
async getAlbum() {
// tslint:disable-next-line: no-use-before-declare
return new Album(await this.data.getAlbum(this.album));
}
async getDuration() {
return await this.data.getDuration(this.path);
}
} }
/** This class represents one artist */ /** This class represents one artist */

View File

@ -1,20 +1,24 @@
<div class="navbar navbar-light bg-light border-top controls-wrapper"> <div class="navbar navbar-light bg-light border-top controls-wrapper">
<span *ngIf="( currentSong | async ) as song" class="songInfo"> <div class="figure-container">
{{ song.name }} <figure *ngIf="currentSong && currentArtist && currentAlbum" class="songInfo">
</span> <img [src]="data.apiUrl + '/image/' + currentAlbum.image" [alt]="'Album art for ' + currentAlbum.name">
<figcaption>{{ currentSong.name }} by {{ currentArtist.name }}</figcaption>
</figure>
</div>
<span class="buttons"> <span class="buttons">
<fa-icon (click)="audio.isCurrentlyPlaying ? audio.pause() : audio.play()" [icon]="audio.isCurrentlyPlaying ? faPause : faPlay"></fa-icon> <fa-icon (click)="audio.isCurrentlyPlaying ? audio.pause() : audio.play()" [icon]="audio.isCurrentlyPlaying ? faPause : faPlay"></fa-icon>
</span> </span>
<span class="time"> <div class="time">
<input type="range" class="form-control-range" id="song-range"> <span class="currentTime">{{ audio.currentTime > 0 ? audio.formatTime(audio.currentTime) : '00:00' }}</span>
</span> <input type="range" min="0" [max]="duration ? duration : 100" [(ngModel)]="audio.time" [value]="audio.currentTime" class="form-control-range" id="song-range">
<span class="duration">{{ duration > 0 ? audio.formatTime(duration) : '00:00' }}</span>
</div>
<span class="volume"> <span class="volume">
<fa-icon [icon]="faMusic" [fixedWidth]="true"></fa-icon> <input min="0" max="1" step="0.05" [(ngModel)]="audio.player.volume" type="range" class="form-control-range" id="volume-range">
<input type="range" class="form-control-range" id="volume-range">
</span> </span>
</div> </div>

View File

@ -2,9 +2,33 @@
width: 100%; width: 100%;
padding: 18px; padding: 18px;
line-height: 1.5em; line-height: 1.5em;
display: grid;
grid-template-columns: 25% 10% 55% 10%
} }
.form-control-range { .form-control-range {
display: inline; display: inline-block;
}
div.time {
display: grid;
grid-template-columns: min-content auto min-content;
span {
padding: 0 5px;
}
}
figure {
margin: 0;
img {
height: 2rem;
display: inline-block;
}
figcaption {
display: inline-block;
}
} }
// #song-range { // #song-range {
// width: 50%; // width: 50%;

View File

@ -1,8 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { faPlay, faPause, faVolumeMute, faVolumeDown, faVolumeUp } from '@fortawesome/free-solid-svg-icons'; import { faPlay, faPause, faVolumeMute, faVolumeDown, faVolumeUp, faMusic } from '@fortawesome/free-solid-svg-icons';
import { AudioService } from 'src/app/services/audio.service'; import { AudioService } from 'src/app/services/audio.service';
import { Song } from '../../classes/entities'; import { Song, Artist, Album } from '../../classes/entities';
import { ApiService } from 'src/app/services/api.service'; import { ApiService } from 'src/app/services/api.service';
import { DataService } from 'src/app/services/data.service';
@Component({ @Component({
selector: 'app-controls', selector: 'app-controls',
@ -17,17 +18,28 @@ export class ControlsComponent implements OnInit {
faVolumeMute = faVolumeMute; faVolumeMute = faVolumeMute;
faVolumeDown = faVolumeDown; faVolumeDown = faVolumeDown;
faVolumeUp = faVolumeUp; faVolumeUp = faVolumeUp;
faMusic = faMusic;
currentSong: Promise<Song>; Math = Math;
NaN = NaN;
currentSong: Song;
currentArtist: Artist;
currentAlbum: Album;
duration: number;
constructor( constructor(
public audio: AudioService, public audio: AudioService,
private api: ApiService private api: ApiService,
public data: DataService
) { } ) { }
ngOnInit() { ngOnInit() {
this.audio.currentSong.subscribe(id => { this.audio.currentSong.subscribe(async id => {
this.currentSong = this.api.getSong(id); this.currentSong = await this.api.getSong(id);
this.currentArtist = await this.currentSong.getArtist();
this.currentAlbum = await this.currentSong.getAlbum();
this.duration = (await this.currentSong.getDuration()).data.result;
}); });
} }

View File

@ -18,11 +18,22 @@ export class AudioService {
return !this.player.paused; return !this.player.paused;
} }
constructor(private data: DataService) { } constructor(private data: DataService) {
// Make sure that the currentTime property is always up to date.
this.player.addEventListener('timeupdate', () => {
this.currentTime = this.player.currentTime;
});
}
player = new Audio(); player = new Audio();
currentSong = new Subject<number>(); currentSong = new Subject<number>();
currentTime: number;
// Use audio.time to set the time
set time(value: number) {
this.player.currentTime = value;
}
/** /**
* The play function sets the playstate of the player to playing. * The play function sets the playstate of the player to playing.
@ -38,6 +49,33 @@ export class AudioService {
this.player.pause(); this.player.pause();
} }
/**
* This function will format the number of seconds to a nices human-readable format.
* @param input The number of seconds.
* @returns a string like: '01:34', standing for 1 minute and 34 seconds.
*/
formatTime(input: number): string {
const minutes = Math.floor(input / 60);
const seconds = Math.floor(input % 60);
return this.addZero(minutes) + ':' + this.addZero(seconds);
}
/**
* This function prepends a 0 when the number is smaller than 10.
* @param input The number
* @returns a string with a 0 prepended if the number is smaller than 10.
*/
addZero(input: number): string {
if (input < 10) {
return '0' + String(input);
} else {
return String(input);
}
}
/** /**
* The setSong function set the song for the player. * The setSong function set the song for the player.
* @param id The id of the song that needs to be played * @param id The id of the song that needs to be played

View File

@ -37,6 +37,10 @@ export class DataService {
return this.axiosInstance.get(`${this.apiUrl}/get/${type}/${id}`); return this.axiosInstance.get(`${this.apiUrl}/get/${type}/${id}`);
} }
getDuration(path: string): Promise<AxiosResponse<ApiData>> {
return this.axiosInstance.get(`${this.apiUrl}/duration/${path}`);
}
/** /**
* @param id The id of the artist. It can be a number representing the id, multiple numbers seperated by commas or the string 'all'. * @param id The id of the artist. It can be a number representing the id, multiple numbers seperated by commas or the string 'all'.
* @returns A Promise<AxiosResponse<object>> containing the returned data from the API. * @returns A Promise<AxiosResponse<object>> containing the returned data from the API.