💥 Finished up controls for so far!
Yay!
This commit is contained in:
parent
319104dee8
commit
29d8d978aa
@ -9,7 +9,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a routerLink="songs" class="list-group-item list-group-item-action bg-light d-flex justify-content-between align-items-center">
|
<a routerLink="songs" class="list-group-item list-group-item-action bg-light d-flex justify-content-between align-items-center">
|
||||||
Songs
|
Songs
|
||||||
<fa-icon [icon]="faMusic" [fixedWidth]="true"></fa-icon>
|
<fa-icon [icon]="faMusic" [fixedWidth]="true"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="albums" class="list-group-item list-group-item-action bg-light d-flex justify-content-between align-items-center">
|
<a routerLink="albums" class="list-group-item list-group-item-action bg-light d-flex justify-content-between align-items-center">
|
||||||
Albums
|
Albums
|
||||||
@ -30,8 +30,8 @@
|
|||||||
<nav class="navbar navbar-light bg-light justify-content-between border-bottom">
|
<nav class="navbar navbar-light bg-light justify-content-between border-bottom">
|
||||||
<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>
|
||||||
@ -43,4 +43,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Music controls -->
|
<!-- Music controls -->
|
||||||
<app-controls class="d-flex"></app-controls>
|
<app-controls class="d-flex"></app-controls>
|
||||||
|
@ -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',
|
||||||
@ -19,15 +20,14 @@ export class AppComponent implements OnInit {
|
|||||||
faChevronLeft = faChevronLeft;
|
faChevronLeft = faChevronLeft;
|
||||||
faChevronRight = faChevronRight;
|
faChevronRight = faChevronRight;
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor(public router: Router) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
this.navToggled = false;
|
this.navToggled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleNav() {
|
toggleNav() {
|
||||||
this.navToggled = (this.navToggled) ? false : true;
|
this.navToggled = (this.navToggled) ? false : true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
@ -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 */
|
||||||
|
@ -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>
|
||||||
|
@ -2,13 +2,37 @@
|
|||||||
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%;
|
||||||
// }
|
// }
|
||||||
// #volume-range {
|
// #volume-range {
|
||||||
// width: 20%;
|
// width: 20%;
|
||||||
// }
|
// }
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user