io-player
Version:
IoPlayer - Audio Player web component
218 lines (212 loc) • 22.1 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, Component, ChangeDetectionStrategy, Input, Output, ViewChild, NgModule } from '@angular/core';
import { BehaviorSubject, from, fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';
import * as i2 from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
class IoPlayerService {
constructor() {
this._isPlaying = false;
this.subscriptions = [];
this._isPlaying$ = new BehaviorSubject(this._isPlaying);
}
ngOnDestroy() {
if (this._isPlaying) {
this.pause();
}
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
/**
* @description initiatize audio player.
*/
init(source) {
this._audio = document.createElement('audio');
this._audio.src = source;
this.audioFinish$.subscribe(() => this.pause());
}
/**
* @description API to play current player.
*/
play() {
if (!this._isPlaying) {
from(this._audio.play()).subscribe(() => {
this.toggleIsPlaying();
});
}
}
/**
* @description API to pause player.
*/
pause() {
if (!this._isPlaying)
return;
this._audio.pause();
this.toggleIsPlaying();
}
forward(offset = 10.0) {
if (!this._isPlaying)
return;
this._audio.currentTime += offset;
}
backward(offset = 10.0) {
if (!this._isPlaying)
return;
this._audio.currentTime -= offset;
}
/**
* @description ask for specific pourcentage position.
*/
readFromPercentage(percentage) {
this._audio.currentTime = this._audio.duration * percentage / 100;
}
/**
* @description switch isPlaying state.
*/
toggleIsPlaying() {
this._isPlaying = !this._isPlaying;
this._isPlaying$.next(this._isPlaying);
}
/**
* @description observable of current percentage position in audio.
*/
get percentageReaded$() {
return fromEvent(this._audio, 'timeupdate').pipe(map(() => {
return (this._audio.currentTime / this._audio.duration) * 100;
}));
}
/**
* @description observable who notify when 'ended' audio event is fire.
*/
get audioFinish$() {
return fromEvent(this._audio, 'ended').pipe(map(() => {
return null;
}));
}
/**
* @description Observable to determine if current sound is playing or not.
*/
get isPlaying$() {
return this._isPlaying$.asObservable();
}
}
IoPlayerService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
IoPlayerService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerService, decorators: [{
type: Injectable
}], ctorParameters: function () { return []; } });
class IoPlayerComponent {
constructor(playerService, changeRef) {
this.playerService = playerService;
this.changeRef = changeRef;
this.isPlaying = false;
this.progression = 0;
this.forwardOffsetStep = 10.0;
this.subscriptions = [];
this.progression$ = new EventEmitter();
}
set fastForward(step) {
this.forwardOffsetStep = parseFloat(step);
}
ngOnInit() {
this.playerService.init(this.src);
this.subscriptions.push(this.playerService.isPlaying$.subscribe(state => {
this.isPlaying = state;
this.changeRef.detectChanges();
}), this.playerService.percentageReaded$.subscribe(state => {
this.progression = state;
this.progression$.emit(state);
this.changeRef.detectChanges();
}));
}
ngOnDestroy() {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
/**
* Click handler on play pause button.
*/
onPlayHandler() {
if (!this.isPlaying) {
this.playerService.play();
}
else {
this.playerService.pause();
}
}
/**
* Click Handler on timeline bar
*/
onChangeTimelinekHandler($event) {
const percentage = Math.floor(($event.offsetX / this.timelineElement.nativeElement.offsetWidth) * 100);
this.playerService.readFromPercentage(percentage);
}
onFastBackwardHandler() {
this.playerService.backward(this.forwardOffsetStep);
}
onFastForwardHandler() {
this.playerService.forward(this.forwardOffsetStep);
}
}
IoPlayerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerComponent, deps: [{ token: IoPlayerService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
IoPlayerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.0.2", type: IoPlayerComponent, selector: "io-player", inputs: { src: "src", cover: "cover", author: "author", song: "song", fastForward: "fastForward" }, outputs: { progression$: "progression" }, providers: [IoPlayerService], viewQueries: [{ propertyName: "coverElement", first: true, predicate: ["coverEl"], descendants: true }, { propertyName: "timelineElement", first: true, predicate: ["timelineBar"], descendants: true }], ngImport: i0, template: "<div class=\"player\" [ngClass]=\"{play: isPlaying}\">\n <div class=\"player__bar\">\n <div class=\"player__album\">\n <div class=\"player__scale-wrapper\" [ngClass]=\"{'isPlaying': isPlaying}\">\n <div class=\"player__albumImg active-song\" #coverEl [ngStyle]=\"{backgroundImage: 'url('+cover+')'}\">\n </div>\n </div>\n </div>\n <div class=\"player__controls\">\n <div class=\"player__prev\" (click)=\"onFastBackwardHandler()\">\n <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M26.695 34.434v31.132L54.5 49.998z\"/><path d=\"M24.07 34.434v31.132c0 2.018 2.222 3.234 3.95 2.267l27.804-15.568c1.706-.955 1.707-3.578 0-4.533L28.02 32.168c-2.957-1.655-5.604 2.88-2.649 4.533l27.805 15.564v-4.533L25.371 63.3l3.95 2.267V34.435c-.001-3.387-5.251-3.387-5.251-.001z\"/><g><path d=\"M55.5 34.434v31.132l27.805-15.568z\"/><path d=\"M52.875 34.434v31.132c0 2.018 2.222 3.234 3.949 2.267 9.27-5.189 18.537-10.379 27.805-15.568 1.705-.955 1.705-3.578 0-4.533L56.824 32.168c-2.957-1.655-5.604 2.88-2.648 4.533l27.803 15.564v-4.533L54.176 63.3l3.949 2.267V34.435c0-3.387-5.25-3.387-5.25-.001z\"/></g></svg>\n </div>\n\n <div class=\"player__play\" (click)=\"onPlayHandler()\">\n <svg class=\"icon play\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\"><path d=\"M51.109 30.335l-36-24A2 2 0 0 0 12 8v48a2.003 2.003 0 0 0 2 2c.388 0 .775-.113 1.109-.336l36-24a2 2 0 0 0 0-3.329z\"/></svg>\n <svg class=\"icon pause\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M22.537 8.046h17.791c1.104 0 2.003.898 2.003 1.993v79.912a2.005 2.005 0 0 1-2.003 2.003h-17.79a2.005 2.005 0 0 1-2.003-2.003V10.04c0-1.095.898-1.993 2.002-1.993zM59.672 8.046h17.8c1.095 0 1.993.898 1.993 1.993v79.912a2.003 2.003 0 0 1-1.993 2.003h-17.8a1.997 1.997 0 0 1-1.993-2.003V10.04c0-1.095.889-1.993 1.993-1.993z\"/></svg>\n </div>\n\n <div class=\"player__next\" (click)=\"onFastForwardHandler()\">\n <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M26.695 34.434v31.132L54.5 49.998z\"/><path d=\"M24.07 34.434v31.132c0 2.018 2.222 3.234 3.95 2.267l27.804-15.568c1.706-.955 1.707-3.578 0-4.533L28.02 32.168c-2.957-1.655-5.604 2.88-2.649 4.533l27.805 15.564v-4.533L25.371 63.3l3.95 2.267V34.435c-.001-3.387-5.251-3.387-5.251-.001z\"/><g><path d=\"M55.5 34.434v31.132l27.805-15.568z\"/><path d=\"M52.875 34.434v31.132c0 2.018 2.222 3.234 3.949 2.267 9.27-5.189 18.537-10.379 27.805-15.568 1.705-.955 1.705-3.578 0-4.533L56.824 32.168c-2.957-1.655-5.604 2.88-2.648 4.533l27.803 15.564v-4.533L54.176 63.3l3.949 2.267V34.435c0-3.387-5.25-3.387-5.25-.001z\"/></g></svg>\n </div>\n </div>\n </div>\n <div class=\"player__timeline\">\n <p class=\"player__author\">\n {{ author }}\n </p>\n <p class=\"player__song\">\n {{ song }}\n </p>\n\n <button class=\"player__timelineHit\" (click)=\"onChangeTimelinekHandler($event)\">\n <div class=\"player__timelineBar\" #timelineBar>\n <div id=\"playhead\" [ngStyle]=\"{width: progression+'%'}\"></div>\n </div>\n </button>\n </div>\n</div>\n", styles: [".player{position:relative;display:inline-block;max-width:420px;margin-top:80px}.player.play .player__timeline{transform:translateY(-100%)}.player.play .player__album:after{box-shadow:0 30px 28px -10px #0003}.player.play .player__album{top:-65px}.player.play .pause{display:inline-block}.player.play .play{display:none}.player.play .player__prev,.player.play .player__next{cursor:pointer}.player.play .player__prev svg,.player.play .player__next svg{fill:#d7dce2}.player.play .player__prev:hover,.player.play .player__next:hover{background:#D7DCE2}.player.play .player__prev:hover svg,.player.play .player__next:hover svg{fill:#fff}.player__album{width:112px;min-width:112px;height:112px;min-height:112px;border-radius:50%;position:relative;top:-50px;transition:all .4s ease-in-out}.player__album:before{content:\"\";width:25px;height:25px;position:absolute;z-index:3;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;border-radius:50%}.player__album:after{content:\"\";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;box-shadow:none;transition:all .3s ease-in-out}.player__scale-wrapper{transform:scale(1);transition:all .4s ease-in-out;width:100%;height:100%}.player__scale-wrapper.isPlaying{transform:scale(1.1)}.player__scale-wrapper.isPlaying .player__albumImg{animation:rotating 4s linear infinite}.player__scale-wrapper .player__albumImg{background-size:cover;background:no-repeat center;width:100%;height:100%;border-radius:50%;position:relative;z-index:2;display:none}.player__scale-wrapper .player__albumImg.active-song{display:block}.player__bar{background:#fff;padding:10px 25px;height:100px;display:flex;justify-content:space-between;border-radius:15px;box-shadow:0 30px 56px 6px #0000001a;position:relative;z-index:3}.player__controls{display:flex;align-items:center;margin-left:22px}.player__play,.player__next,.player__prev{cursor:default;height:80px;width:80px;display:flex;justify-content:center;align-items:center;border-radius:15px;transition:all .2s ease-in-out;position:relative}.player__play svg,.player__next svg,.player__prev svg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:inline-block;width:2em;height:2em;font-size:30px;fill:#d7dce2;transition:all .2s ease-in-out}.player__play svg.pause,.player__next svg.pause,.player__prev svg.pause{display:none}.player__prev svg,.player__next svg{fill:#f3f3f3}.player__prev{transform:rotate(180deg)}.player__play{cursor:pointer}.player__play svg{font-size:20px}.player__play:hover{background:#D7DCE2}.player__play:hover svg{fill:#fff}.player__timeline{background:#fff6fb;height:95px;border-radius:15px;position:absolute;bottom:0;left:10px;right:10px;transform:translateY(0);transition:all .3s ease-in-out;z-index:1;padding:0 5px 0 160px;flex-direction:column;justify-content:center}.player__timelineBar{background:#E7E7E7;width:100%;height:4px;border-radius:15px;margin-top:0;position:relative}.player__timelineHit{border:none;margin:0;padding:0;display:block;background:transparent;width:100%;height:15px}.player__timelineHit:focus{-webkit-tap-highlight-color:rgba(0,0,0,0);outline:0}.player #playhead{position:absolute;top:0;left:0;border-radius:15px;width:0;height:100%;background:#fd6d94}.player__author{line-height:1;font-weight:bold;margin-bottom:6px;margin-top:15px}.player__song{line-height:1;margin:0;font-size:12px;color:#949494}@keyframes rotating{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rotating{animation:rotating 4s linear infinite}\n"], directives: [{ type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerComponent, decorators: [{
type: Component,
args: [{ selector: 'io-player', providers: [IoPlayerService], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"player\" [ngClass]=\"{play: isPlaying}\">\n <div class=\"player__bar\">\n <div class=\"player__album\">\n <div class=\"player__scale-wrapper\" [ngClass]=\"{'isPlaying': isPlaying}\">\n <div class=\"player__albumImg active-song\" #coverEl [ngStyle]=\"{backgroundImage: 'url('+cover+')'}\">\n </div>\n </div>\n </div>\n <div class=\"player__controls\">\n <div class=\"player__prev\" (click)=\"onFastBackwardHandler()\">\n <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M26.695 34.434v31.132L54.5 49.998z\"/><path d=\"M24.07 34.434v31.132c0 2.018 2.222 3.234 3.95 2.267l27.804-15.568c1.706-.955 1.707-3.578 0-4.533L28.02 32.168c-2.957-1.655-5.604 2.88-2.649 4.533l27.805 15.564v-4.533L25.371 63.3l3.95 2.267V34.435c-.001-3.387-5.251-3.387-5.251-.001z\"/><g><path d=\"M55.5 34.434v31.132l27.805-15.568z\"/><path d=\"M52.875 34.434v31.132c0 2.018 2.222 3.234 3.949 2.267 9.27-5.189 18.537-10.379 27.805-15.568 1.705-.955 1.705-3.578 0-4.533L56.824 32.168c-2.957-1.655-5.604 2.88-2.648 4.533l27.803 15.564v-4.533L54.176 63.3l3.949 2.267V34.435c0-3.387-5.25-3.387-5.25-.001z\"/></g></svg>\n </div>\n\n <div class=\"player__play\" (click)=\"onPlayHandler()\">\n <svg class=\"icon play\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\"><path d=\"M51.109 30.335l-36-24A2 2 0 0 0 12 8v48a2.003 2.003 0 0 0 2 2c.388 0 .775-.113 1.109-.336l36-24a2 2 0 0 0 0-3.329z\"/></svg>\n <svg class=\"icon pause\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M22.537 8.046h17.791c1.104 0 2.003.898 2.003 1.993v79.912a2.005 2.005 0 0 1-2.003 2.003h-17.79a2.005 2.005 0 0 1-2.003-2.003V10.04c0-1.095.898-1.993 2.002-1.993zM59.672 8.046h17.8c1.095 0 1.993.898 1.993 1.993v79.912a2.003 2.003 0 0 1-1.993 2.003h-17.8a1.997 1.997 0 0 1-1.993-2.003V10.04c0-1.095.889-1.993 1.993-1.993z\"/></svg>\n </div>\n\n <div class=\"player__next\" (click)=\"onFastForwardHandler()\">\n <svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><path d=\"M26.695 34.434v31.132L54.5 49.998z\"/><path d=\"M24.07 34.434v31.132c0 2.018 2.222 3.234 3.95 2.267l27.804-15.568c1.706-.955 1.707-3.578 0-4.533L28.02 32.168c-2.957-1.655-5.604 2.88-2.649 4.533l27.805 15.564v-4.533L25.371 63.3l3.95 2.267V34.435c-.001-3.387-5.251-3.387-5.251-.001z\"/><g><path d=\"M55.5 34.434v31.132l27.805-15.568z\"/><path d=\"M52.875 34.434v31.132c0 2.018 2.222 3.234 3.949 2.267 9.27-5.189 18.537-10.379 27.805-15.568 1.705-.955 1.705-3.578 0-4.533L56.824 32.168c-2.957-1.655-5.604 2.88-2.648 4.533l27.803 15.564v-4.533L54.176 63.3l3.949 2.267V34.435c0-3.387-5.25-3.387-5.25-.001z\"/></g></svg>\n </div>\n </div>\n </div>\n <div class=\"player__timeline\">\n <p class=\"player__author\">\n {{ author }}\n </p>\n <p class=\"player__song\">\n {{ song }}\n </p>\n\n <button class=\"player__timelineHit\" (click)=\"onChangeTimelinekHandler($event)\">\n <div class=\"player__timelineBar\" #timelineBar>\n <div id=\"playhead\" [ngStyle]=\"{width: progression+'%'}\"></div>\n </div>\n </button>\n </div>\n</div>\n", styles: [".player{position:relative;display:inline-block;max-width:420px;margin-top:80px}.player.play .player__timeline{transform:translateY(-100%)}.player.play .player__album:after{box-shadow:0 30px 28px -10px #0003}.player.play .player__album{top:-65px}.player.play .pause{display:inline-block}.player.play .play{display:none}.player.play .player__prev,.player.play .player__next{cursor:pointer}.player.play .player__prev svg,.player.play .player__next svg{fill:#d7dce2}.player.play .player__prev:hover,.player.play .player__next:hover{background:#D7DCE2}.player.play .player__prev:hover svg,.player.play .player__next:hover svg{fill:#fff}.player__album{width:112px;min-width:112px;height:112px;min-height:112px;border-radius:50%;position:relative;top:-50px;transition:all .4s ease-in-out}.player__album:before{content:\"\";width:25px;height:25px;position:absolute;z-index:3;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;border-radius:50%}.player__album:after{content:\"\";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;box-shadow:none;transition:all .3s ease-in-out}.player__scale-wrapper{transform:scale(1);transition:all .4s ease-in-out;width:100%;height:100%}.player__scale-wrapper.isPlaying{transform:scale(1.1)}.player__scale-wrapper.isPlaying .player__albumImg{animation:rotating 4s linear infinite}.player__scale-wrapper .player__albumImg{background-size:cover;background:no-repeat center;width:100%;height:100%;border-radius:50%;position:relative;z-index:2;display:none}.player__scale-wrapper .player__albumImg.active-song{display:block}.player__bar{background:#fff;padding:10px 25px;height:100px;display:flex;justify-content:space-between;border-radius:15px;box-shadow:0 30px 56px 6px #0000001a;position:relative;z-index:3}.player__controls{display:flex;align-items:center;margin-left:22px}.player__play,.player__next,.player__prev{cursor:default;height:80px;width:80px;display:flex;justify-content:center;align-items:center;border-radius:15px;transition:all .2s ease-in-out;position:relative}.player__play svg,.player__next svg,.player__prev svg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:inline-block;width:2em;height:2em;font-size:30px;fill:#d7dce2;transition:all .2s ease-in-out}.player__play svg.pause,.player__next svg.pause,.player__prev svg.pause{display:none}.player__prev svg,.player__next svg{fill:#f3f3f3}.player__prev{transform:rotate(180deg)}.player__play{cursor:pointer}.player__play svg{font-size:20px}.player__play:hover{background:#D7DCE2}.player__play:hover svg{fill:#fff}.player__timeline{background:#fff6fb;height:95px;border-radius:15px;position:absolute;bottom:0;left:10px;right:10px;transform:translateY(0);transition:all .3s ease-in-out;z-index:1;padding:0 5px 0 160px;flex-direction:column;justify-content:center}.player__timelineBar{background:#E7E7E7;width:100%;height:4px;border-radius:15px;margin-top:0;position:relative}.player__timelineHit{border:none;margin:0;padding:0;display:block;background:transparent;width:100%;height:15px}.player__timelineHit:focus{-webkit-tap-highlight-color:rgba(0,0,0,0);outline:0}.player #playhead{position:absolute;top:0;left:0;border-radius:15px;width:0;height:100%;background:#fd6d94}.player__author{line-height:1;font-weight:bold;margin-bottom:6px;margin-top:15px}.player__song{line-height:1;margin:0;font-size:12px;color:#949494}@keyframes rotating{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rotating{animation:rotating 4s linear infinite}\n"] }]
}], ctorParameters: function () { return [{ type: IoPlayerService }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { src: [{
type: Input
}], cover: [{
type: Input
}], author: [{
type: Input
}], song: [{
type: Input
}], fastForward: [{
type: Input
}], progression$: [{
type: Output,
args: ['progression']
}], coverElement: [{
type: ViewChild,
args: ['coverEl']
}], timelineElement: [{
type: ViewChild,
args: ['timelineBar']
}] } });
class IoPlayerModule {
constructor(injector) {
this.injector = injector;
const customElement = createCustomElement(IoPlayerComponent, { injector });
customElements.define('io-player', customElement);
}
// eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method
ngDoBootstrap() {
}
}
IoPlayerModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerModule, deps: [{ token: i0.Injector }], target: i0.ɵɵFactoryTarget.NgModule });
IoPlayerModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerModule, declarations: [IoPlayerComponent], imports: [BrowserModule], exports: [IoPlayerComponent] });
IoPlayerModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerModule, providers: [], imports: [[
BrowserModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: IoPlayerModule, decorators: [{
type: NgModule,
args: [{
declarations: [
IoPlayerComponent
],
entryComponents: [
IoPlayerComponent
],
imports: [
BrowserModule
],
exports: [
IoPlayerComponent
],
providers: [],
bootstrap: []
}]
}], ctorParameters: function () { return [{ type: i0.Injector }]; } });
/**
* Generated bundle index. Do not edit.
*/
export { IoPlayerComponent, IoPlayerModule };