added custom player
added routing with two routes: home and player moved most of app component to main component. app component currently just manages the top toolbarpull/11/head
parent
d595de5786
commit
8e7bb4ba3b
@ -0,0 +1,15 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { MainComponent } from './main/main.component';
|
||||||
|
import { PlayerComponent } from './player/player.component';
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: 'home', component: MainComponent },
|
||||||
|
{ path: 'player', component: PlayerComponent},
|
||||||
|
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forRoot(routes, { useHash: true })],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AppRoutingModule { }
|
||||||
@ -1,68 +1,13 @@
|
|||||||
.demo-card {
|
.flex-row {
|
||||||
margin: 16px;
|
display: flex;
|
||||||
}
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
.demo-basic {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-basic .mat-card-content {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-toolbar.top {
|
|
||||||
height: 60px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*::ng-deep .mat-form-field-placeholder{
|
|
||||||
|
|
||||||
transform: scale(.75) translateY(20px) !important;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
.big {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centered {
|
|
||||||
margin: 0 auto;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-full-width {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-form-field.mat-form-field {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
position: absolute;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: -28px;
|
|
||||||
margin-top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.make-room-for-spinner {
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.equal-sizes {
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-card-title {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-clear-button {
|
.flex-column {
|
||||||
position: absolute;
|
display: flex;
|
||||||
right: -10px;
|
flex-direction: column;
|
||||||
top: 5px;
|
flex-basis: 100%;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
@ -1,121 +1,15 @@
|
|||||||
<mat-toolbar color="primary" class="top">
|
<mat-toolbar color="primary" class="top">
|
||||||
<table width="100%" height="100%">
|
<div class="flex-row" width="100%" height="100%">
|
||||||
<td class="topbar" style="text-align: left; left:0px; font-size: 15px">
|
<div class="flex-column" style="text-align: left; margin-top: 1px;">
|
||||||
</td>
|
<button (click)="goBack()" *ngIf="router.url.split(';')[0] === '/player'" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
|
||||||
<td class="topbar" style="text-align: center">
|
</div>
|
||||||
<div style="margin-top: 14px">{{topBarTitle}}</div>
|
<div class="flex-column" style="text-align: center; margin-top: 5px;">
|
||||||
</td>
|
<div>{{topBarTitle}}</div>
|
||||||
<td class="topbar" style="text-align: right">
|
</div>
|
||||||
|
<div class="flex-column" style="text-align: right">
|
||||||
</td>
|
|
||||||
</table>
|
</div>
|
||||||
</mat-toolbar>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="big demo-basic">
|
|
||||||
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;">
|
|
||||||
<mat-card-title>
|
|
||||||
Youtube Downloader
|
|
||||||
</mat-card-title>
|
|
||||||
<mat-card-content>
|
|
||||||
<div style="position: relative;">
|
|
||||||
<form class="example-form">
|
|
||||||
<mat-form-field class="example-full-width">
|
|
||||||
<input matInput (ngModelChange)="inputChanged($event)" [(ngModel)]="url" placeholder="URL" type="url" name="url" [formControl]="urlForm" required #urlinput>
|
|
||||||
<mat-error *ngIf="urlError || urlForm.invalid">Please enter a valid URL!</mat-error>
|
|
||||||
<button class="input-clear-button" mat-icon-button (click)="clearInput()"><mat-icon>clear</mat-icon></button>
|
|
||||||
</mat-form-field>
|
|
||||||
<span *ngIf="results_showing">
|
|
||||||
<span *ngFor="let result of results">
|
|
||||||
<mat-card style="height: 120px; border-radius: 0px">
|
|
||||||
<div class="search-card-title">
|
|
||||||
{{result.title}}
|
|
||||||
</div>
|
|
||||||
<div style="font-size: 12px">
|
|
||||||
{{result.uploaded}}
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<button mat-flat-button color="primary" style="float: left;" (click)="useURL(result.videoUrl)">Use URL</button>
|
|
||||||
<button mat-stroked-button color="primary" (click)="visitURL(result.videoUrl)" style="float: right">View</button>
|
|
||||||
</mat-card>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
<br/>
|
|
||||||
<mat-checkbox [(ngModel)]="audioOnly" style="float: left; margin-top: -12px">Only Audio</mat-checkbox>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
<mat-card-actions>
|
|
||||||
<button style="margin-left: 8px; margin-bottom: 8px" (click)="downloadClicked()" [disabled]="downloadingfile" type="submit" mat-stroked-button
|
|
||||||
color="primary">Download</button>
|
|
||||||
</mat-card-actions>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<div class="centered big" id="bar_div" *ngIf="downloadingfile;else nofile">
|
|
||||||
<div [ngClass]="(determinateProgress && percentDownloaded === 100)?'make-room-for-spinner':'equal-sizes'" style="display: inline-block; width: 100%; padding-left: 20px" *ngIf="determinateProgress;else indeterminateprogress">
|
|
||||||
<mat-progress-bar mode="determinate" value="{{percentDownloaded}}"></mat-progress-bar>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="determinateProgress && percentDownloaded === 100" class="spinner">
|
|
||||||
<mat-spinner [diameter]="25"></mat-spinner>
|
|
||||||
</div>
|
</div>
|
||||||
<ng-template #indeterminateprogress>
|
</mat-toolbar>
|
||||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
|
||||||
</ng-template>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
<ng-template #nofile>
|
|
||||||
|
|
||||||
</ng-template>
|
|
||||||
<div style="margin: 20px" *ngIf="fileManagerEnabled">
|
|
||||||
<mat-accordion>
|
|
||||||
<mat-expansion-panel class="big">
|
|
||||||
<mat-expansion-panel-header>
|
|
||||||
<mat-panel-title>
|
|
||||||
Audio
|
|
||||||
</mat-panel-title>
|
|
||||||
<mat-panel-description>
|
|
||||||
Your audio files are here
|
|
||||||
</mat-panel-description>
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<div *ngIf="mp3s.length > 0;else nomp3s">
|
|
||||||
<mat-grid-list (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
|
||||||
<mat-grid-tile *ngFor="let file of mp3s; index as i;">
|
|
||||||
<app-file-card (removeFile)="removeFromMp3($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
|
||||||
[length]="file.duration" [isAudio]="true"></app-file-card>
|
|
||||||
</mat-grid-tile>
|
|
||||||
</mat-grid-list>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</mat-expansion-panel>
|
|
||||||
<mat-expansion-panel class="big">
|
|
||||||
<mat-expansion-panel-header>
|
|
||||||
<mat-panel-title>
|
|
||||||
Video
|
|
||||||
</mat-panel-title>
|
|
||||||
<mat-panel-description>
|
|
||||||
Your video files are here
|
|
||||||
</mat-panel-description>
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<div *ngIf="mp4s.length > 0;else nomp4s">
|
|
||||||
<mat-grid-list (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
|
||||||
<mat-grid-tile *ngFor="let file of mp4s; index as i;">
|
|
||||||
<app-file-card (removeFile)="removeFromMp4($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
|
||||||
[length]="file.duration" [isAudio]="false"></app-file-card>
|
|
||||||
</mat-grid-tile>
|
|
||||||
</mat-grid-list>
|
|
||||||
</div>
|
|
||||||
</mat-expansion-panel>
|
|
||||||
</mat-accordion>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-template #nomp3s>
|
|
||||||
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #nomp4s>
|
|
||||||
|
|
||||||
</ng-template>
|
<router-outlet></router-outlet>
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
.demo-card {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-basic {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-basic .mat-card-content {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-toolbar.top {
|
||||||
|
height: 60px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*::ng-deep .mat-form-field-placeholder{
|
||||||
|
|
||||||
|
transform: scale(.75) translateY(20px) !important;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
.big {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
margin: 0 auto;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-form-field.mat-form-field {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: -28px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.make-room-for-spinner {
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equal-sizes {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-card-title {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-clear-button {
|
||||||
|
position: absolute;
|
||||||
|
right: -10px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
<br/>
|
||||||
|
<div class="big demo-basic">
|
||||||
|
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;">
|
||||||
|
<mat-card-title>
|
||||||
|
Youtube Downloader
|
||||||
|
</mat-card-title>
|
||||||
|
<mat-card-content>
|
||||||
|
<div style="position: relative;">
|
||||||
|
<form class="example-form">
|
||||||
|
<mat-form-field class="example-full-width">
|
||||||
|
<input matInput (ngModelChange)="inputChanged($event)" [(ngModel)]="url" placeholder="URL" type="url" name="url" [formControl]="urlForm" required #urlinput>
|
||||||
|
<mat-error *ngIf="urlError || urlForm.invalid">Please enter a valid URL!</mat-error>
|
||||||
|
<button class="input-clear-button" mat-icon-button (click)="clearInput()"><mat-icon>clear</mat-icon></button>
|
||||||
|
</mat-form-field>
|
||||||
|
<span *ngIf="results_showing">
|
||||||
|
<span *ngFor="let result of results">
|
||||||
|
<mat-card style="height: 120px; border-radius: 0px">
|
||||||
|
<div class="search-card-title">
|
||||||
|
{{result.title}}
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 12px">
|
||||||
|
{{result.uploaded}}
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<button mat-flat-button color="primary" style="float: left;" (click)="useURL(result.videoUrl)">Use URL</button>
|
||||||
|
<button mat-stroked-button color="primary" (click)="visitURL(result.videoUrl)" style="float: right">View</button>
|
||||||
|
</mat-card>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
<br/>
|
||||||
|
<mat-checkbox [(ngModel)]="audioOnly" style="float: left; margin-top: -12px">Only Audio</mat-checkbox>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button style="margin-left: 8px; margin-bottom: 8px" (click)="downloadClicked()" [disabled]="downloadingfile" type="submit" mat-stroked-button
|
||||||
|
color="primary">Download</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="centered big" id="bar_div" *ngIf="downloadingfile;else nofile">
|
||||||
|
<div [ngClass]="(determinateProgress && percentDownloaded === 100)?'make-room-for-spinner':'equal-sizes'" style="display: inline-block; width: 100%; padding-left: 20px" *ngIf="determinateProgress;else indeterminateprogress">
|
||||||
|
<mat-progress-bar mode="determinate" value="{{percentDownloaded}}"></mat-progress-bar>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="determinateProgress && percentDownloaded === 100" class="spinner">
|
||||||
|
<mat-spinner [diameter]="25"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
<ng-template #indeterminateprogress>
|
||||||
|
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||||
|
</ng-template>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<ng-template #nofile>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
<div style="margin: 20px" *ngIf="fileManagerEnabled">
|
||||||
|
<mat-accordion>
|
||||||
|
<mat-expansion-panel class="big">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Audio
|
||||||
|
</mat-panel-title>
|
||||||
|
<mat-panel-description>
|
||||||
|
Your audio files are here
|
||||||
|
</mat-panel-description>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<div *ngIf="mp3s.length > 0;else nomp3s">
|
||||||
|
<mat-grid-list (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
|
<mat-grid-tile *ngFor="let file of mp3s; index as i;">
|
||||||
|
<app-file-card (removeFile)="removeFromMp3($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||||
|
[length]="file.duration" [isAudio]="true"></app-file-card>
|
||||||
|
</mat-grid-tile>
|
||||||
|
</mat-grid-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</mat-expansion-panel>
|
||||||
|
<mat-expansion-panel class="big">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title>
|
||||||
|
Video
|
||||||
|
</mat-panel-title>
|
||||||
|
<mat-panel-description>
|
||||||
|
Your video files are here
|
||||||
|
</mat-panel-description>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<div *ngIf="mp4s.length > 0;else nomp4s">
|
||||||
|
<mat-grid-list (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||||
|
<mat-grid-tile *ngFor="let file of mp4s; index as i;">
|
||||||
|
<app-file-card (removeFile)="removeFromMp4($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||||
|
[length]="file.duration" [isAudio]="false"></app-file-card>
|
||||||
|
</mat-grid-tile>
|
||||||
|
</mat-grid-list>
|
||||||
|
</div>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
</mat-accordion>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #nomp3s>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #nomp4s>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MainComponent } from './main.component';
|
||||||
|
|
||||||
|
describe('MainComponent', () => {
|
||||||
|
let component: MainComponent;
|
||||||
|
let fixture: ComponentFixture<MainComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ MainComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MainComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,338 @@
|
|||||||
|
import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
|
||||||
|
import {PostsService} from '../posts.services';
|
||||||
|
import {FileCardComponent} from '../file-card/file-card.component';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import {FormControl, Validators} from '@angular/forms';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {MatSnackBar} from '@angular/material';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
|
import 'rxjs/add/operator/mapTo';
|
||||||
|
import 'rxjs/add/operator/toPromise';
|
||||||
|
import 'rxjs/add/observable/fromEvent'
|
||||||
|
import 'rxjs/add/operator/filter'
|
||||||
|
import 'rxjs/add/operator/debounceTime'
|
||||||
|
import 'rxjs/add/operator/do'
|
||||||
|
import 'rxjs/add/operator/switch'
|
||||||
|
import { YoutubeSearchService, Result } from '../youtube-search.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './main.component.html',
|
||||||
|
styleUrls: ['./main.component.css']
|
||||||
|
})
|
||||||
|
export class MainComponent implements OnInit {
|
||||||
|
iOS = false;
|
||||||
|
|
||||||
|
determinateProgress = false;
|
||||||
|
downloadingfile = false;
|
||||||
|
audioOnly: boolean;
|
||||||
|
urlError = false;
|
||||||
|
path = '';
|
||||||
|
url = '';
|
||||||
|
exists = '';
|
||||||
|
percentDownloaded: number;
|
||||||
|
fileManagerEnabled = false;
|
||||||
|
downloadOnlyMode = false;
|
||||||
|
baseStreamPath;
|
||||||
|
audioFolderPath;
|
||||||
|
videoFolderPath;
|
||||||
|
|
||||||
|
// youtube api
|
||||||
|
youtubeSearchEnabled = false;
|
||||||
|
youtubeAPIKey = null;
|
||||||
|
results_loading = false;
|
||||||
|
results_showing = true;
|
||||||
|
results = [];
|
||||||
|
|
||||||
|
mp3s: any[] = [];
|
||||||
|
mp4s: any[] = [];
|
||||||
|
files_cols = (window.innerWidth <= 450) ? 2 : 4;
|
||||||
|
|
||||||
|
urlForm = new FormControl('', [Validators.required]);
|
||||||
|
|
||||||
|
@ViewChild('urlinput', { read: ElementRef, static: false }) urlInput: ElementRef;
|
||||||
|
|
||||||
|
constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
||||||
|
private router: Router) {
|
||||||
|
this.audioOnly = false;
|
||||||
|
|
||||||
|
|
||||||
|
// loading config
|
||||||
|
this.postsService.loadNavItems().subscribe(result => { // loads settings
|
||||||
|
const backendUrl = result['YoutubeDLMaterial']['Host']['backendurl'];
|
||||||
|
this.fileManagerEnabled = result['YoutubeDLMaterial']['Extra']['file_manager_enabled'];
|
||||||
|
this.downloadOnlyMode = result['YoutubeDLMaterial']['Extra']['download_only_mode'];
|
||||||
|
this.baseStreamPath = result['YoutubeDLMaterial']['Downloader']['path-base'];
|
||||||
|
this.audioFolderPath = result['YoutubeDLMaterial']['Downloader']['path-audio'];
|
||||||
|
this.videoFolderPath = result['YoutubeDLMaterial']['Downloader']['path-video'];
|
||||||
|
this.youtubeSearchEnabled = result['YoutubeDLMaterial']['API'] && result['YoutubeDLMaterial']['API']['use_youtube_API'];
|
||||||
|
this.youtubeAPIKey = this.youtubeSearchEnabled ? result['YoutubeDLMaterial']['API']['youtube_API_key'] : null;
|
||||||
|
|
||||||
|
this.postsService.path = backendUrl;
|
||||||
|
this.postsService.startPath = backendUrl;
|
||||||
|
this.postsService.startPathSSL = backendUrl;
|
||||||
|
|
||||||
|
if (this.fileManagerEnabled) {
|
||||||
|
this.getMp3s();
|
||||||
|
this.getMp4s();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.youtubeSearchEnabled && this.youtubeAPIKey) {
|
||||||
|
this.youtubeSearch.initializeAPI(this.youtubeAPIKey);
|
||||||
|
this.attachToInput();
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// file manager stuff
|
||||||
|
|
||||||
|
getMp3s() {
|
||||||
|
this.postsService.getMp3s().subscribe(result => {
|
||||||
|
const mp3s = result['mp3s'];
|
||||||
|
this.mp3s = mp3s;
|
||||||
|
}, error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMp4s() {
|
||||||
|
this.postsService.getMp4s().subscribe(result => {
|
||||||
|
const mp4s = result['mp4s'];
|
||||||
|
this.mp4s = mp4s;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public goToFile(name, isAudio) {
|
||||||
|
if (isAudio) {
|
||||||
|
this.downloadHelperMp3(name, false, true);
|
||||||
|
} else {
|
||||||
|
this.downloadHelperMp4(name, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFromMp3(name: string) {
|
||||||
|
for (let i = 0; i < this.mp3s.length; i++) {
|
||||||
|
if (this.mp3s[i].id === name) {
|
||||||
|
this.mp3s.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFromMp4(name: string) {
|
||||||
|
// console.log(name);
|
||||||
|
// console.log(this.mp4s);
|
||||||
|
for (let i = 0; i < this.mp4s.length; i++) {
|
||||||
|
if (this.mp4s[i].id === name) {
|
||||||
|
this.mp4s.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// app initialization.
|
||||||
|
ngOnInit() {
|
||||||
|
this.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window['MSStream'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// download helpers
|
||||||
|
|
||||||
|
downloadHelperMp3(name, is_playlist = false, forceView = false) {
|
||||||
|
this.downloadingfile = false;
|
||||||
|
|
||||||
|
// if download only mode, just download the file. no redirect
|
||||||
|
if (forceView === false && this.downloadOnlyMode && !this.iOS) {
|
||||||
|
if (is_playlist) {
|
||||||
|
for (let i = 0; i < name.length; i++) {
|
||||||
|
this.downloadAudioFile(name[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.downloadAudioFile(name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (is_playlist) {
|
||||||
|
this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'audio'}]);
|
||||||
|
// window.location.href = this.baseStreamPath + this.audioFolderPath + name[0] + '.mp3';
|
||||||
|
} else {
|
||||||
|
window.location.href = this.baseStreamPath + this.audioFolderPath + name + '.mp3';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloads mp3s
|
||||||
|
if (this.fileManagerEnabled) {
|
||||||
|
this.getMp3s();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadHelperMp4(name, is_playlist = false, forceView = false) {
|
||||||
|
this.downloadingfile = false;
|
||||||
|
|
||||||
|
// if download only mode, just download the file. no redirect
|
||||||
|
if (forceView === false && this.downloadOnlyMode) {
|
||||||
|
if (is_playlist) {
|
||||||
|
for (let i = 0; i < name.length; i++) {
|
||||||
|
this.downloadVideoFile(name[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.downloadVideoFile(name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (is_playlist) {
|
||||||
|
this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'video'}]);
|
||||||
|
// window.location.href = this.baseStreamPath + this.videoFolderPath + name[0] + '.mp4';
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/player', {fileNames: name, type: 'video'}]);
|
||||||
|
// window.location.href = this.baseStreamPath + this.videoFolderPath + name + '.mp4';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloads mp4s
|
||||||
|
if (this.fileManagerEnabled) {
|
||||||
|
this.getMp4s();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// download click handler
|
||||||
|
downloadClicked() {
|
||||||
|
if (this.ValidURL(this.url)) {
|
||||||
|
this.urlError = false;
|
||||||
|
this.path = '';
|
||||||
|
|
||||||
|
if (this.audioOnly) {
|
||||||
|
this.downloadingfile = true;
|
||||||
|
this.postsService.makeMP3(this.url).subscribe(posts => {
|
||||||
|
const is_playlist = !!(posts['file_names']);
|
||||||
|
this.path = is_playlist ? posts['file_names'] : posts['audiopathEncoded'];
|
||||||
|
if (this.path !== '-1') {
|
||||||
|
this.downloadHelperMp3(this.path, is_playlist);
|
||||||
|
}
|
||||||
|
}, error => { // can't access server
|
||||||
|
this.downloadingfile = false;
|
||||||
|
this.openSnackBar('Download failed!', 'OK.');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.downloadingfile = true;
|
||||||
|
this.postsService.makeMP4(this.url).subscribe(posts => {
|
||||||
|
const is_playlist = !!(posts['file_names']);
|
||||||
|
this.path = is_playlist ? posts['file_names'] : posts['videopathEncoded'];
|
||||||
|
if (this.path !== '-1') {
|
||||||
|
this.downloadHelperMp4(this.path, is_playlist);
|
||||||
|
}
|
||||||
|
}, error => { // can't access server
|
||||||
|
this.downloadingfile = false;
|
||||||
|
this.openSnackBar('Download failed!', 'OK.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.urlError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadAudioFile(name) {
|
||||||
|
this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => {
|
||||||
|
const blob: Blob = res;
|
||||||
|
saveAs(blob, name + '.mp3');
|
||||||
|
|
||||||
|
// tell server to delete the file once downloaded
|
||||||
|
this.postsService.deleteFile(name, true).subscribe(delRes => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadVideoFile(name) {
|
||||||
|
this.postsService.downloadFileFromServer(name, 'video').subscribe(res => {
|
||||||
|
const blob: Blob = res;
|
||||||
|
saveAs(blob, name + '.mp4');
|
||||||
|
|
||||||
|
// tell server to delete the file once downloaded
|
||||||
|
this.postsService.deleteFile(name, false).subscribe(delRes => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInput() {
|
||||||
|
this.url = '';
|
||||||
|
this.results_showing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onInputBlur() {
|
||||||
|
this.results_showing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitURL(url) {
|
||||||
|
window.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
useURL(url) {
|
||||||
|
this.results_showing = false;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputChanged(new_val) {
|
||||||
|
if (new_val === '') {
|
||||||
|
this.results_showing = false;
|
||||||
|
} else {
|
||||||
|
if (this.ValidURL(new_val)) {
|
||||||
|
this.results_showing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if url is a valid URL
|
||||||
|
ValidURL(str) {
|
||||||
|
// tslint:disable-next-line: max-line-length
|
||||||
|
const strRegex = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/;
|
||||||
|
const re = new RegExp(strRegex);
|
||||||
|
return re.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// snackbar helper
|
||||||
|
public openSnackBar(message: string, action: string) {
|
||||||
|
this.snackBar.open(message, action, {
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
attachToInput() {
|
||||||
|
Observable.fromEvent(this.urlInput.nativeElement, 'keyup')
|
||||||
|
.map((e: any) => e.target.value) // extract the value of input
|
||||||
|
.filter((text: string) => text.length > 1) // filter out if empty
|
||||||
|
.debounceTime(250) // only once every 250ms
|
||||||
|
.do(() => this.results_loading = true) // enable loading
|
||||||
|
.map((query: string) => this.youtubeSearch.search(query))
|
||||||
|
.switch() // act on the return of the search
|
||||||
|
.subscribe(
|
||||||
|
(results: Result[]) => {
|
||||||
|
// console.log(results);
|
||||||
|
this.results_loading = false;
|
||||||
|
if (results && results.length > 0) {
|
||||||
|
this.results = results;
|
||||||
|
this.results_showing = true;
|
||||||
|
} else {
|
||||||
|
this.results_showing = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err: any) => {
|
||||||
|
console.log(err)
|
||||||
|
this.results_loading = false;
|
||||||
|
this.results_showing = false;
|
||||||
|
},
|
||||||
|
() => { // on completion
|
||||||
|
this.results_loading = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onResize(event) {
|
||||||
|
this.files_cols = (event.target.innerWidth <= 450) ? 2 : 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
.video-player {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-styles {
|
||||||
|
height: 50px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-styles {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-button-toggle-label-content {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
<div *ngIf="playlist.length > 0; else loading">
|
||||||
|
<vg-player (onPlayerReady)="onPlayerReady($event)" [style.background-color]="(type === 'audio') ? 'transparent' : 'black'">
|
||||||
|
<video [ngClass]="(type === 'audio') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="media" [src]="currentItem.src" id="singleVideo" preload="auto" controls>
|
||||||
|
</video>
|
||||||
|
</vg-player>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div style="width: 100%;">
|
||||||
|
<mat-button-toggle-group style="width: 80%; left: 9%" vertical name="videoSelect" aria-label="Video Select" #group="matButtonToggleGroup">
|
||||||
|
<mat-button-toggle *ngFor="let name of fileNames; let i = index" [checked]="currentItem.title === name" (click)="onClickPlaylistItem(playlist[i], i)" class="toggle-button" [value]="name">{{name}}</mat-button-toggle>
|
||||||
|
</mat-button-toggle-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PlayerComponent } from './player.component';
|
||||||
|
|
||||||
|
describe('PlayerComponent', () => {
|
||||||
|
let component: PlayerComponent;
|
||||||
|
let fixture: ComponentFixture<PlayerComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ PlayerComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PlayerComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { VgAPI } from 'videogular2/compiled/core';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
export interface IMedia {
|
||||||
|
title: string;
|
||||||
|
src: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-player',
|
||||||
|
templateUrl: './player.component.html',
|
||||||
|
styleUrls: ['./player.component.css']
|
||||||
|
})
|
||||||
|
export class PlayerComponent implements OnInit {
|
||||||
|
|
||||||
|
playlist: Array<IMedia> = [];
|
||||||
|
|
||||||
|
currentIndex = 0;
|
||||||
|
currentItem: IMedia = null;
|
||||||
|
api: VgAPI;
|
||||||
|
|
||||||
|
// params
|
||||||
|
fileNames: string[];
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
baseStreamPath = null;
|
||||||
|
audioFolderPath = null;
|
||||||
|
videoFolderPath = null;
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.fileNames = this.route.snapshot.paramMap.get('fileNames').split('|nvr|');
|
||||||
|
this.type = this.route.snapshot.paramMap.get('type');
|
||||||
|
|
||||||
|
// loading config
|
||||||
|
this.postsService.loadNavItems().subscribe(result => { // loads settings
|
||||||
|
this.baseStreamPath = result['YoutubeDLMaterial']['Downloader']['path-base'];
|
||||||
|
this.audioFolderPath = result['YoutubeDLMaterial']['Downloader']['path-audio'];
|
||||||
|
this.videoFolderPath = result['YoutubeDLMaterial']['Downloader']['path-video'];
|
||||||
|
let fileType = null;
|
||||||
|
if (this.type === 'audio') {
|
||||||
|
fileType = 'audio/mp3';
|
||||||
|
} else if (this.type === 'video') {
|
||||||
|
fileType = 'video/mp4';
|
||||||
|
} else {
|
||||||
|
// error
|
||||||
|
console.error('Must have valid file type! Use \'audio\' or \video\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.fileNames.length; i++) {
|
||||||
|
const fileName = this.fileNames[i];
|
||||||
|
const baseLocation = (this.type === 'audio') ? this.audioFolderPath : this.videoFolderPath;
|
||||||
|
const fullLocation = this.baseStreamPath + baseLocation + fileName + (this.type === 'audio' ? '.mp3' : '.mp4');
|
||||||
|
const mediaObject: IMedia = {
|
||||||
|
title: fileName,
|
||||||
|
src: fullLocation,
|
||||||
|
type: fileType
|
||||||
|
}
|
||||||
|
console.log(mediaObject);
|
||||||
|
this.playlist.push(mediaObject);
|
||||||
|
}
|
||||||
|
this.currentItem = this.playlist[this.currentIndex];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getFileInfos();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private postsService: PostsService, private route: ActivatedRoute) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlayerReady(api: VgAPI) {
|
||||||
|
this.api = api;
|
||||||
|
|
||||||
|
this.api.getDefaultMedia().subscriptions.loadedMetadata.subscribe(this.playVideo.bind(this));
|
||||||
|
this.api.getDefaultMedia().subscriptions.ended.subscribe(this.nextVideo.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
nextVideo() {
|
||||||
|
if (this.currentIndex === this.playlist.length - 1) {
|
||||||
|
// dont continue playing
|
||||||
|
// this.currentIndex = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentIndex++;
|
||||||
|
this.currentItem = this.playlist[ this.currentIndex ];
|
||||||
|
}
|
||||||
|
|
||||||
|
playVideo() {
|
||||||
|
this.api.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickPlaylistItem(item: IMedia, index: number) {
|
||||||
|
console.log('new current item is ' + item.title + ' at index ' + index);
|
||||||
|
this.currentIndex = index;
|
||||||
|
this.currentItem = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileInfos() {
|
||||||
|
this.postsService.getFileInfo(this.fileNames, this.type).subscribe(res => {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue