From 8e7bb4ba3b40f8e1e1f2f41116cf04f9727eec22 Mon Sep 17 00:00:00 2001 From: Isaac Grynsztein Date: Sat, 15 Feb 2020 02:13:21 -0500 Subject: [PATCH] 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 toolbar --- angular.json | 6 +- package.json | 2 + src/app/app-routing.module.ts | 15 + src/app/app.component.css | 75 +---- src/app/app.component.html | 128 +------- src/app/app.component.ts | 267 +--------------- src/app/app.module.ts | 22 +- src/app/file-card/file-card.component.html | 2 +- src/app/file-card/file-card.component.ts | 4 +- src/app/main/main.component.css | 68 +++++ src/app/main/main.component.html | 107 +++++++ src/app/main/main.component.spec.ts | 25 ++ src/app/main/main.component.ts | 338 +++++++++++++++++++++ src/app/player/player.component.css | 19 ++ src/app/player/player.component.html | 14 + src/app/player/player.component.spec.ts | 25 ++ src/app/player/player.component.ts | 108 +++++++ src/app/posts.services.ts | 4 + 18 files changed, 778 insertions(+), 451 deletions(-) create mode 100644 src/app/app-routing.module.ts create mode 100644 src/app/main/main.component.css create mode 100644 src/app/main/main.component.html create mode 100644 src/app/main/main.component.spec.ts create mode 100644 src/app/main/main.component.ts create mode 100644 src/app/player/player.component.css create mode 100644 src/app/player/player.component.html create mode 100644 src/app/player/player.component.spec.ts create mode 100644 src/app/player/player.component.ts diff --git a/angular.json b/angular.json index 9ab9eda..6093aa3 100644 --- a/angular.json +++ b/angular.json @@ -24,7 +24,8 @@ "src/backend" ], "styles": [ - "src/styles.css" + "src/styles.css", + "../node_modules/videogular2/fonts/videogular.css" ], "scripts": [] }, @@ -74,7 +75,8 @@ "tsConfig": "src/tsconfig.spec.json", "scripts": [], "styles": [ - "src/styles.css" + "src/styles.css", + "../node_modules/videogular2/fonts/videogular.css" ], "assets": [ "src/assets", diff --git a/package.json b/package.json index 8aef587..7dacf09 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "rxjs": "^6.5.3", "rxjs-compat": "^6.0.0-rc.0", "tslib": "^1.10.0", + "videogular2": "^7.0.1", "zone.js": "~0.9.1" }, "devDependencies": { @@ -37,6 +38,7 @@ "@angular/cli": "^8.3.12", "@angular/compiler-cli": "^8.2.11", "@angular/language-service": "^8.2.11", + "@types/core-js": "^2.5.2", "@types/file-saver": "^2.0.1", "@types/jasmine": "2.5.45", "@types/node": "~6.0.60", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts new file mode 100644 index 0000000..c9d3181 --- /dev/null +++ b/src/app/app-routing.module.ts @@ -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 { } diff --git a/src/app/app.component.css b/src/app/app.component.css index d0f9435..d7ef823 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,68 +1,13 @@ -.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 { +.flex-row { + display: flex; + flex-direction: row; + flex-wrap: wrap; 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; + +.flex-column { + display: flex; + flex-direction: column; + flex-basis: 100%; + flex: 1; } \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 31a405a..331f8e0 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,121 +1,15 @@ - - - - -
- -
{{topBarTitle}}
-
+
+
+ +
+
+
{{topBarTitle}}
+
+
-
+ +
-
- -
- - - Youtube Downloader - - -
-
- - - Please enter a valid URL! - - - - - -
- {{result.title}} -
-
- {{result.uploaded}} -
-
- - -
-
-
-
-
- Only Audio - -
-
- - - -
-
-
-
-
- -
-
-
- -
- - - -
-
- - - -
- - - - - Audio - - - Your audio files are here - - -
- - - - - -
- -
- - - - Video - - - Your video files are here - - -
- - - - - -
-
-
-
- - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5b1a843..b256e73 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,6 +15,7 @@ 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', @@ -54,282 +55,26 @@ export class AppComponent implements OnInit { @ViewChild('urlinput', { read: ElementRef, static: false }) urlInput: ElementRef; - constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar) { + constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar, + public router: Router) { this.audioOnly = false; // loading config this.postsService.loadNavItems().subscribe(result => { // loads settings - const backendUrl = result['YoutubeDLMaterial']['Host']['backendurl']; this.topBarTitle = result['YoutubeDLMaterial']['Extra']['title_top']; - 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) { - 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) { - window.location.href = this.baseStreamPath + this.videoFolderPath + name[0] + '.mp4'; - } else { - 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; + goBack() { + this.router.navigate(['/home']); } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index fb6fd9e..d5aff3a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,7 +4,8 @@ import {MatNativeDateModule, MatRadioModule, MatInputModule, MatButtonModule, Ma MatSnackBarModule, MatCardModule, MatSelectModule, MatToolbarModule, MatCheckboxModule, MatGridListModule, MatProgressBarModule, MatExpansionModule, MatGridList, - MatProgressSpinnerModule} from '@angular/material'; + MatProgressSpinnerModule, + MatButtonToggleModule} from '@angular/material'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { AppComponent } from './app.component'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; @@ -14,11 +15,20 @@ import { PostsService } from 'app/posts.services'; import {APP_BASE_HREF} from '@angular/common'; import { FileCardComponent } from './file-card/file-card.component'; import {RouterModule} from '@angular/router'; +import { AppRoutingModule } from './app-routing.module'; +import { MainComponent } from './main/main.component'; +import { PlayerComponent } from './player/player.component'; +import {VgCoreModule} from 'videogular2/compiled/core'; +import {VgControlsModule} from 'videogular2/compiled/controls'; +import {VgOverlayPlayModule} from 'videogular2/compiled/overlay-play'; +import {VgBufferingModule} from 'videogular2/compiled/buffering'; @NgModule({ declarations: [ AppComponent, - FileCardComponent + FileCardComponent, + MainComponent, + PlayerComponent ], imports: [ BrowserModule, @@ -43,7 +53,13 @@ import {RouterModule} from '@angular/router'; MatExpansionModule, MatProgressBarModule, MatProgressSpinnerModule, - RouterModule + MatButtonToggleModule, + VgCoreModule, + VgControlsModule, + VgOverlayPlayModule, + VgBufferingModule, + RouterModule, + AppRoutingModule ], providers: [PostsService], bootstrap: [AppComponent] diff --git a/src/app/file-card/file-card.component.html b/src/app/file-card/file-card.component.html index 8513572..ddb48e0 100644 --- a/src/app/file-card/file-card.component.html +++ b/src/app/file-card/file-card.component.html @@ -1,7 +1,7 @@
- {{title}} + {{title}}
ID: {{name}}
diff --git a/src/app/file-card/file-card.component.ts b/src/app/file-card/file-card.component.ts index ce18ca6..f35ea4e 100644 --- a/src/app/file-card/file-card.component.ts +++ b/src/app/file-card/file-card.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit, Input, Output } from '@angular/core'; import {PostsService} from '../posts.services'; import {MatSnackBar} from '@angular/material'; -import {AppComponent} from '../app.component'; import {EventEmitter} from '@angular/core'; +import { MainComponent } from 'app/main/main.component'; @Component({ selector: 'app-file-card', @@ -18,7 +18,7 @@ export class FileCardComponent implements OnInit { @Input() isAudio = true; @Output() removeFile: EventEmitter = new EventEmitter(); - constructor(private postsService: PostsService, public snackBar: MatSnackBar, public appComponent: AppComponent) { } + constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent) { } ngOnInit() { } diff --git a/src/app/main/main.component.css b/src/app/main/main.component.css new file mode 100644 index 0000000..d0f9435 --- /dev/null +++ b/src/app/main/main.component.css @@ -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; +} \ No newline at end of file diff --git a/src/app/main/main.component.html b/src/app/main/main.component.html new file mode 100644 index 0000000..f173278 --- /dev/null +++ b/src/app/main/main.component.html @@ -0,0 +1,107 @@ +
+
+ + + Youtube Downloader + + +
+
+ + + Please enter a valid URL! + + + + + +
+ {{result.title}} +
+
+ {{result.uploaded}} +
+
+ + +
+
+
+
+
+ Only Audio + +
+
+ + + +
+
+
+
+
+ +
+
+
+ +
+ + + +
+
+ + + +
+ + + + + Audio + + + Your audio files are here + + +
+ + + + + +
+ +
+ + + + Video + + + Your video files are here + + +
+ + + + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/app/main/main.component.spec.ts b/src/app/main/main.component.spec.ts new file mode 100644 index 0000000..0878044 --- /dev/null +++ b/src/app/main/main.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MainComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MainComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts new file mode 100644 index 0000000..b9a5b7c --- /dev/null +++ b/src/app/main/main.component.ts @@ -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; + } +} + diff --git a/src/app/player/player.component.css b/src/app/player/player.component.css new file mode 100644 index 0000000..266c172 --- /dev/null +++ b/src/app/player/player.component.css @@ -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; +} \ No newline at end of file diff --git a/src/app/player/player.component.html b/src/app/player/player.component.html new file mode 100644 index 0000000..2526df5 --- /dev/null +++ b/src/app/player/player.component.html @@ -0,0 +1,14 @@ +
+ + + + +
+ +
+ + {{name}} + +
+
\ No newline at end of file diff --git a/src/app/player/player.component.spec.ts b/src/app/player/player.component.spec.ts new file mode 100644 index 0000000..d08e5be --- /dev/null +++ b/src/app/player/player.component.spec.ts @@ -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; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PlayerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PlayerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts new file mode 100644 index 0000000..93fd1d1 --- /dev/null +++ b/src/app/player/player.component.ts @@ -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 = []; + + 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 => { + + }); + } + +} diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 32943cc..05e79a3 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -79,6 +79,10 @@ export class PostsService { downloadFileFromServer(fileName, type) { return this.http.post(this.path + 'downloadFile', {fileName: fileName, type: type}, {responseType: 'blob'}); } + + getFileInfo(fileNames, type) { + return this.http.post(this.path + 'getVideoInfos', {fileNames: fileNames, type: type}); + } }