mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-09 04:50:02 +03:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
042baa418b | ||
|
|
deb928da12 | ||
|
|
a7f5cc01d3 | ||
|
|
414b6a26d9 | ||
|
|
c069672e62 | ||
|
|
167d9dafa2 | ||
|
|
9302084f60 | ||
|
|
ac0199f596 | ||
|
|
8e8ab7ac6c | ||
|
|
f06c9ba44a | ||
|
|
6e593472d9 | ||
|
|
0bddbda36d | ||
|
|
23feb05fab | ||
|
|
a0eff4d96d | ||
|
|
b87b49d77b | ||
|
|
c05026aa15 | ||
|
|
883df63d2f | ||
|
|
da1d49b541 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -46,4 +46,5 @@ backend/node_modules/*
|
||||
YoutubeDL-Material/node_modules/*
|
||||
backend/video/*
|
||||
backend/audio/*
|
||||
backend/db.json
|
||||
src/assets/default.json
|
||||
|
||||
33
README.md
33
README.md
@@ -1,6 +1,6 @@
|
||||
# YoutubeDL-Material
|
||||
|
||||
YoutubeDL-Material is a material design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 5](https://angular.io/) for the frontend, and [Nodejs](https://nodejs.org/) on the backend.
|
||||
YoutubeDL-Material is a material design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 8](https://angular.io/) for the frontend, and [Nodejs](https://nodejs.org/) on the backend.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -8,11 +8,15 @@ Check out the prerequisites, and go to the installation section. Easy as pie!
|
||||
|
||||
Here's an image of what it'll look like once you're done:
|
||||
|
||||

|
||||

|
||||
|
||||
With optional file management enabled (default):
|
||||
|
||||

|
||||

|
||||
|
||||
Dark mode:
|
||||
|
||||

|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -34,6 +38,29 @@ Once the configuration is done, type `sudo nodejs app.js`. This will run the bac
|
||||
|
||||
If you experience problems, know that it's usually caused by a configuration problem. The first thing you should do is check the console. To get there, right click anywhere on the page and click "Inspect element." Then on the menu that pops up, click console. Look at the error there, and try to investigate.
|
||||
|
||||
### Configuration
|
||||
|
||||
Here is an explanation for the configuration entries. Check out the [default config](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/backend/config/default.json) for more context.
|
||||
|
||||
| Config item | Description | Default |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| frontendurl | URL to the webserver hosting YTDL-Material | "http://example.com" |
|
||||
| backendurl | URL to the YTDL-Material's backend, should include port 17442 | "http://example.com:17442/" |
|
||||
| use-encryption | true if you intend to use SSL encryption (https) | false |
|
||||
| cert-file-path | Cert file path - required if using encryption | "/etc/letsencrypt/live/example.com/fullchain.pem" |
|
||||
| key-file-path | Private key file path - required if using encryption | "/etc/letsencrypt/live/example.com/privkey.pem" |
|
||||
| path-base | Audio/video stream URL. Usually the same as backendurl | "http://example.com:17442/" |
|
||||
| path-audio | Path to audio folder for saved mp3s | "audio/" |
|
||||
| path-video | Path to video folder for saved mp4s | "video/" |
|
||||
| title_top | Title shown on the top toolbar | "Youtube Downloader" |
|
||||
| file_manager_enabled | true if you want to use the file manager | true |
|
||||
| allow_quality_select | true if you want to select a videos quality level before downloading | true |
|
||||
| download_only_mode | true if you want files to directly download to the client with no media player | false |
|
||||
| use_youtube_API | true if you want to use the Youtube API which is used for YT searches | false |
|
||||
| youtube_API_key | Youtube API key. Required if use_youtube_API is enabled | "" |
|
||||
| default_theme | Default theme to use. Options are "default" and "dark" | "default" |
|
||||
| allow_theme_change | true if you want the icon in the top toolbar that toggles dark mode | true |
|
||||
|
||||
## Deployment
|
||||
|
||||
If you'd like to install YoutubeDL-Material, go to the Installation section. If you want to build it yourself and/or develop the repository, then this section is for you.
|
||||
|
||||
@@ -37,6 +37,14 @@ var basePath = config.get("YoutubeDLMaterial.Downloader.path-base");
|
||||
var audioFolderPath = config.get("YoutubeDLMaterial.Downloader.path-audio");
|
||||
var videoFolderPath = config.get("YoutubeDLMaterial.Downloader.path-video");
|
||||
var downloadOnlyMode = config.get("YoutubeDLMaterial.Extra.download_only_mode")
|
||||
var useDefaultDownloadingAgent = config.get("YoutubeDLMaterial.Advanced.use_default_downloading_agent");
|
||||
var customDownloadingAgent = config.get("YoutubeDLMaterial.Advanced.custom_downloading_agent");
|
||||
var validDownloadingAgents = [
|
||||
'aria2c'
|
||||
]
|
||||
if (!useDefaultDownloadingAgent && validDownloadingAgents.indexOf(customDownloadingAgent) !== -1 ) {
|
||||
console.log(`INFO: Using non-default downloading agent \'${customDownloadingAgent}\'`)
|
||||
}
|
||||
|
||||
var descriptors = {};
|
||||
|
||||
@@ -347,9 +355,15 @@ function getVideoInfos(fileNames) {
|
||||
|
||||
// currently only works for single urls
|
||||
async function getUrlInfos(urls) {
|
||||
let startDate = Date.now();
|
||||
let result = [];
|
||||
return new Promise(resolve => {
|
||||
youtubedl.exec(urls.join(' '), ['--external-downloader', 'aria2c', '--dump-json'], {}, (err, output) => {
|
||||
youtubedl.exec(urls.join(' '), ['--dump-json'], {}, (err, output) => {
|
||||
if (debugMode) {
|
||||
let new_date = Date.now();
|
||||
let difference = (new_date - startDate)/1000;
|
||||
console.log(`URL info retrieval delay: ${difference} seconds.`);
|
||||
}
|
||||
if (err) {
|
||||
console.log('Error during parsing:' + err);
|
||||
resolve(null);
|
||||
@@ -360,8 +374,8 @@ async function getUrlInfos(urls) {
|
||||
result = try_putput;
|
||||
} catch(e) {
|
||||
// probably multiple urls
|
||||
console.log('failed to parse');
|
||||
console.log(output);
|
||||
console.log('failed to parse for urls starting with ' + urls[0]);
|
||||
// console.log(output);
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
@@ -377,7 +391,7 @@ app.post('/tomp3', function(req, res) {
|
||||
var customQualityConfiguration = req.body.customQualityConfiguration;
|
||||
var maxBitrate = req.body.maxBitrate;
|
||||
|
||||
let downloadConfig = ['--external-downloader', 'aria2c', '-o', path + audiopath + ".mp3", '-x', '--audio-format', 'mp3', '--write-info-json', '--print-json']
|
||||
let downloadConfig = ['-o', path + audiopath + ".mp3", '-x', '--audio-format', 'mp3', '--write-info-json', '--print-json']
|
||||
let qualityPath = '';
|
||||
|
||||
if (customQualityConfiguration) {
|
||||
@@ -391,6 +405,10 @@ app.post('/tomp3', function(req, res) {
|
||||
downloadConfig.splice(2, 0, qualityPath);
|
||||
}
|
||||
|
||||
if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') {
|
||||
downloadConfig.splice(0, 0, '--external-downloader', 'aria2c');
|
||||
}
|
||||
|
||||
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
|
||||
if (debugMode) {
|
||||
let new_date = Date.now();
|
||||
@@ -399,6 +417,7 @@ app.post('/tomp3', function(req, res) {
|
||||
}
|
||||
if (err) {
|
||||
audiopath = "-1";
|
||||
console.log(err.stderr);
|
||||
res.sendStatus(500);
|
||||
throw err;
|
||||
} else if (output) {
|
||||
@@ -411,8 +430,8 @@ app.post('/tomp3', function(req, res) {
|
||||
output_json = null;
|
||||
}
|
||||
if (!output_json) {
|
||||
// only run on first go
|
||||
return;
|
||||
// if invalid, continue onto the next
|
||||
continue;
|
||||
}
|
||||
var file_name = output_json['_filename'].replace(/^.*[\\\/]/, '');
|
||||
var alternate_file_name = file_name.substring(0, file_name.length-4);
|
||||
@@ -448,7 +467,11 @@ app.post('/tomp4', function(req, res) {
|
||||
qualityPath = `bestvideo[height=${selectedHeight}]+bestaudio/best[height=${selectedHeight}]`;
|
||||
}
|
||||
|
||||
youtubedl.exec(url, ['--external-downloader', 'aria2c', '-o', path + videopath + ".mp4", '-f', qualityPath, '--write-info-json', '--print-json'], {}, function(err, output) {
|
||||
let downloadConfig = ['-o', path + videopath + ".mp4", '-f', qualityPath, '--write-info-json', '--print-json']
|
||||
if (!useDefaultDownloadingAgent && customDownloadingAgent === 'aria2c') {
|
||||
downloadConfig.splice(0, 0, '--external-downloader', 'aria2c');
|
||||
}
|
||||
youtubedl.exec(url, downloadConfig, {}, function(err, output) {
|
||||
if (debugMode) {
|
||||
let new_date = Date.now();
|
||||
let difference = (new_date - date)/1000;
|
||||
@@ -456,6 +479,7 @@ app.post('/tomp4', function(req, res) {
|
||||
}
|
||||
if (err) {
|
||||
videopath = "-1";
|
||||
console.log(err.stderr);
|
||||
res.sendStatus(500);
|
||||
throw err;
|
||||
} else if (output) {
|
||||
@@ -639,6 +663,32 @@ app.post('/createPlaylist', async (req, res) => {
|
||||
})
|
||||
});
|
||||
|
||||
app.post('/updatePlaylist', async (req, res) => {
|
||||
let playlistID = req.body.playlistID;
|
||||
let fileNames = req.body.fileNames;
|
||||
let type = req.body.type;
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
db.get(`playlists.${type}`)
|
||||
.find({id: playlistID})
|
||||
.assign({fileNames: fileNames})
|
||||
.write();
|
||||
/*console.log('success!');
|
||||
let new_val = db.get(`playlists.${type}`)
|
||||
.find({id: playlistID})
|
||||
.value();
|
||||
console.log(new_val);*/
|
||||
success = true;
|
||||
} catch(e) {
|
||||
console.error(`Failed to find playlist with ID ${playlistID}`);
|
||||
}
|
||||
|
||||
res.send({
|
||||
success: success
|
||||
})
|
||||
});
|
||||
|
||||
app.post('/deletePlaylist', async (req, res) => {
|
||||
let playlistID = req.body.playlistID;
|
||||
let type = req.body.type;
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
"Themes": {
|
||||
"default_theme": "default",
|
||||
"allow_theme_change": true
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
"Themes": {
|
||||
"default_theme": "default",
|
||||
"allow_theme_change": true
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
<div [style.background]="postsService.theme ? postsService.theme.background_color : null" style="width: 100%; height: 100%;">
|
||||
<div [style.background]="postsService.theme ? postsService.theme.background_color : null" style="width: 100%; min-height: 100%;">
|
||||
<mat-toolbar color="primary" class="top">
|
||||
<div class="flex-row" width="100%" height="100%">
|
||||
<div class="flex-column" style="text-align: left; margin-top: 1px;">
|
||||
|
||||
@@ -37,7 +37,7 @@ export class AppComponent implements OnInit {
|
||||
@ViewChild('urlinput', { read: ElementRef, static: false }) urlInput: ElementRef;
|
||||
|
||||
constructor(public postsService: PostsService, public snackBar: MatSnackBar,
|
||||
public router: Router, public overlayContainer: OverlayContainer) {
|
||||
public router: Router, public overlayContainer: OverlayContainer, private elementRef: ElementRef) {
|
||||
|
||||
// loading config
|
||||
this.postsService.loadNavItems().subscribe(result => { // loads settings
|
||||
@@ -76,6 +76,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
localStorage.setItem('theme', theme);
|
||||
this.elementRef.nativeElement.ownerDocument.body.style.backgroundColor = this.THEMES_CONFIG[theme]['background_color'];
|
||||
} else {
|
||||
console.error('Invalid theme: ' + theme);
|
||||
return;
|
||||
|
||||
@@ -3,17 +3,16 @@ import { NgModule } from '@angular/core';
|
||||
import {MatNativeDateModule, MatRadioModule, MatInputModule, MatButtonModule, MatSidenavModule, MatIconModule, MatListModule,
|
||||
MatSnackBarModule, MatCardModule, MatSelectModule, MatToolbarModule, MatCheckboxModule, MatGridListModule,
|
||||
MatProgressBarModule, MatExpansionModule,
|
||||
MatGridList,
|
||||
MatProgressSpinnerModule,
|
||||
MatButtonToggleModule,
|
||||
MatDialogModule} from '@angular/material';
|
||||
import {DragDropModule} from '@angular/cdk/drag-drop';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import { AppComponent } from './app.component';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClientModule, HttpClient } from '@angular/common/http';
|
||||
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';
|
||||
@@ -24,8 +23,14 @@ import {VgControlsModule} from 'videogular2/compiled/controls';
|
||||
import {VgOverlayPlayModule} from 'videogular2/compiled/overlay-play';
|
||||
import {VgBufferingModule} from 'videogular2/compiled/buffering';
|
||||
import { InputDialogComponent } from './input-dialog/input-dialog.component';
|
||||
import { LazyLoadImageModule } from 'ng-lazyload-image';
|
||||
import { LazyLoadImageModule, IsVisibleProps } from 'ng-lazyload-image';
|
||||
import { NgxContentLoadingModule } from 'ngx-content-loading';
|
||||
import { audioFilesMouseHovering, videoFilesMouseHovering } from './main/main.component';
|
||||
import { CreatePlaylistComponent } from './create-playlist/create-playlist.component';
|
||||
|
||||
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
||||
return (element.id === 'video' ? videoFilesMouseHovering : audioFilesMouseHovering);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -33,7 +38,8 @@ import { NgxContentLoadingModule } from 'ngx-content-loading';
|
||||
FileCardComponent,
|
||||
MainComponent,
|
||||
PlayerComponent,
|
||||
InputDialogComponent
|
||||
InputDialogComponent,
|
||||
CreatePlaylistComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@@ -60,17 +66,19 @@ import { NgxContentLoadingModule } from 'ngx-content-loading';
|
||||
MatProgressSpinnerModule,
|
||||
MatButtonToggleModule,
|
||||
MatDialogModule,
|
||||
DragDropModule,
|
||||
VgCoreModule,
|
||||
VgControlsModule,
|
||||
VgOverlayPlayModule,
|
||||
VgBufferingModule,
|
||||
LazyLoadImageModule,
|
||||
LazyLoadImageModule.forRoot({ isVisible }),
|
||||
NgxContentLoadingModule,
|
||||
RouterModule,
|
||||
AppRoutingModule,
|
||||
],
|
||||
entryComponents: [
|
||||
InputDialogComponent
|
||||
InputDialogComponent,
|
||||
CreatePlaylistComponent
|
||||
],
|
||||
providers: [PostsService],
|
||||
bootstrap: [AppComponent]
|
||||
|
||||
19
src/app/create-playlist/create-playlist.component.html
Normal file
19
src/app/create-playlist/create-playlist.component.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<h4 mat-dialog-title>Create a playlist</h4>
|
||||
<form>
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<input [(ngModel)]="name" matInput placeholder="Name" type="text" required aria-required [ngModelOptions]="{standalone: true}">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<mat-label>{{(type === 'audio') ? 'Audio files' : 'Videos'}}</mat-label>
|
||||
<mat-select [formControl]="filesSelect" multiple required aria-required>
|
||||
<mat-option *ngFor="let file of filesToSelectFrom" [value]="file.id">{{file.id}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div *ngIf="create_in_progress" style="float: left"><mat-spinner [diameter]="25"></mat-spinner></div>
|
||||
<button (click)="createPlaylist()" [disabled]="!name || !filesSelect.value || filesSelect.value.length === 0" color="primary" style="float: right" mat-flat-button>Create</button>
|
||||
25
src/app/create-playlist/create-playlist.component.spec.ts
Normal file
25
src/app/create-playlist/create-playlist.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreatePlaylistComponent } from './create-playlist.component';
|
||||
|
||||
describe('CreatePlaylistComponent', () => {
|
||||
let component: CreatePlaylistComponent;
|
||||
let fixture: ComponentFixture<CreatePlaylistComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CreatePlaylistComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CreatePlaylistComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
58
src/app/create-playlist/create-playlist.component.ts
Normal file
58
src/app/create-playlist/create-playlist.component.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-playlist',
|
||||
templateUrl: './create-playlist.component.html',
|
||||
styleUrls: ['./create-playlist.component.scss']
|
||||
})
|
||||
export class CreatePlaylistComponent implements OnInit {
|
||||
// really "createPlaylistDialogComponent"
|
||||
|
||||
filesToSelectFrom = null;
|
||||
type = null;
|
||||
filesSelect = new FormControl();
|
||||
name = '';
|
||||
|
||||
create_in_progress = false;
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private postsService: PostsService,
|
||||
public dialogRef: MatDialogRef<CreatePlaylistComponent>) { }
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data) {
|
||||
this.filesToSelectFrom = this.data.filesToSelectFrom;
|
||||
this.type = this.data.type;
|
||||
}
|
||||
}
|
||||
|
||||
createPlaylist() {
|
||||
const thumbnailURL = this.getThumbnailURL();
|
||||
this.create_in_progress = true;
|
||||
this.postsService.createPlaylist(this.name, this.filesSelect.value, this.type, thumbnailURL).subscribe(res => {
|
||||
this.create_in_progress = false;
|
||||
if (res['success']) {
|
||||
this.dialogRef.close(true);
|
||||
} else {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getThumbnailURL() {
|
||||
for (let i = 0; i < this.filesToSelectFrom.length; i++) {
|
||||
const file = this.filesToSelectFrom[i];
|
||||
if (file.id === this.filesSelect.value[0]) {
|
||||
// different services store the thumbnail in different places
|
||||
if (file.thumbnailURL) { return file.thumbnailURL };
|
||||
if (file.thumbnail) { return file.thumbnail };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
<br/>
|
||||
<span class="max-two-lines">ID: {{name}}</span>
|
||||
<div *ngIf="isPlaylist">Count: {{count}}</div>
|
||||
<div class="img-div">
|
||||
<img class="image" [lazyLoad]="thumbnailURL" (onLoad)="imageLoaded($event)" alt="Thumbnail">
|
||||
<div *ngIf="!image_errored && thumbnailURL" class="img-div">
|
||||
<img class="image" (error) ="onImgError($event)" [id]="type" [lazyLoad]="thumbnailURL" [customObservable]="scrollAndLoad" (onLoad)="imageLoaded($event)" alt="Thumbnail">
|
||||
<span *ngIf="!image_loaded">
|
||||
<ngx-content-loading [width]="500" [height]="360">
|
||||
<svg:g ngx-rect width="500" height="360" y="0" x="0" rx="4" ry="4"></svg:g>
|
||||
|
||||
@@ -3,6 +3,8 @@ import {PostsService} from '../posts.services';
|
||||
import {MatSnackBar} from '@angular/material';
|
||||
import {EventEmitter} from '@angular/core';
|
||||
import { MainComponent } from 'app/main/main.component';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
import 'rxjs/add/observable/merge';
|
||||
|
||||
@Component({
|
||||
selector: 'app-file-card',
|
||||
@@ -21,8 +23,18 @@ export class FileCardComponent implements OnInit {
|
||||
@Input() count = null;
|
||||
type;
|
||||
image_loaded = false;
|
||||
image_errored = false;
|
||||
|
||||
constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent) { }
|
||||
scrollSubject;
|
||||
scrollAndLoad;
|
||||
|
||||
constructor(private postsService: PostsService, public snackBar: MatSnackBar, public mainComponent: MainComponent) {
|
||||
this.scrollSubject = new Subject();
|
||||
this.scrollAndLoad = Observable.merge(
|
||||
Observable.fromEvent(window, 'scroll'),
|
||||
this.scrollSubject
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.type = this.isAudio ? 'audio' : 'video';
|
||||
@@ -44,6 +56,14 @@ export class FileCardComponent implements OnInit {
|
||||
|
||||
}
|
||||
|
||||
onImgError(event) {
|
||||
this.image_errored = true;
|
||||
}
|
||||
|
||||
onHoverResponse() {
|
||||
this.scrollSubject.next();
|
||||
}
|
||||
|
||||
imageLoaded(loaded) {
|
||||
this.image_loaded = true;
|
||||
}
|
||||
|
||||
@@ -111,4 +111,8 @@ mat-form-field.mat-form-field {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.add-playlist-button {
|
||||
float: right;
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="row">
|
||||
<div [ngClass]="allowQualitySelect ? 'col-sm-9' : null" class="col-12">
|
||||
<mat-form-field color="accent" class="example-full-width">
|
||||
<input style="padding-right: 25px;" matInput (ngModelChange)="inputChanged($event)" [(ngModel)]="url" [placeholder]="'URL' + (youtubeSearchEnabled ? ' or search' : '')" type="url" name="url" [formControl]="urlForm" required #urlinput>
|
||||
<input style="padding-right: 25px;" matInput (ngModelChange)="inputChanged($event)" [(ngModel)]="url" [placeholder]="'URL' + (youtubeSearchEnabled ? ' or search' : '')" 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>
|
||||
@@ -21,12 +21,12 @@
|
||||
<mat-label>Quality</mat-label>
|
||||
<mat-select [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedQuality">
|
||||
<ng-container *ngFor="let option of qualityOptions[(audioOnly) ? 'audio' : 'video']">
|
||||
<mat-option *ngIf="option.value === '' || url && cachedAvailableFormats[url] && cachedAvailableFormats[url] && cachedAvailableFormats[url][(audioOnly) ? 'audio' : 'video'][option.value]" [value]="option.value">
|
||||
<mat-option *ngIf="option.value === '' || url && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats'] && cachedAvailableFormats[url]['formats'][(audioOnly) ? 'audio' : 'video'][option.value]" [value]="option.value">
|
||||
{{option.label}}
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
<div class="spinner-div" *ngIf="formats_loading && !cachedAvailableFormats[url]">
|
||||
<div class="spinner-div" *ngIf="url !== '' && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats_loading']">
|
||||
<mat-spinner [diameter]="25"></mat-spinner>
|
||||
</div>
|
||||
</mat-form-field>
|
||||
@@ -80,7 +80,7 @@
|
||||
</ng-template>
|
||||
<div style="margin: 20px" *ngIf="fileManagerEnabled">
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel class="big">
|
||||
<mat-expansion-panel (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
Audio
|
||||
@@ -92,26 +92,30 @@
|
||||
<div *ngIf="mp3s.length > 0;else nomp3s">
|
||||
<mat-grid-list style="margin-bottom: 15px;" (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"
|
||||
<app-file-card #audiofilecard (removeFile)="removeFromMp3($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||
[length]="file.duration" [isAudio]="true"></app-file-card>
|
||||
<mat-progress-bar *ngIf="downloading_content['audio'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
<mat-divider *ngIf="playlists.audio.length > 0"></mat-divider>
|
||||
<div style="width: 100%; text-align: center; margin-top: 10px;" *ngIf="playlists.audio.length > 0">
|
||||
<mat-divider></mat-divider>
|
||||
<div style="width: 100%; text-align: center; margin-top: 10px;">
|
||||
<h6>Playlists</h6>
|
||||
</div>
|
||||
<mat-grid-list *ngIf="playlists.audio.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||
<mat-grid-tile *ngFor="let playlist of playlists.audio; let i = index;">
|
||||
<app-file-card (removeFile)="removePlaylistMp3(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||
<app-file-card #audiofilecard (removeFile)="removePlaylistMp3(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||
[length]="null" [isAudio]="true" [isPlaylist]="true" [count]="playlist.fileNames.length"></app-file-card>
|
||||
<mat-progress-bar *ngIf="downloading_content['audio'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
<div class="add-playlist-button"><button (click)="openCreatePlaylistDialog('audio')" mat-fab><mat-icon>add</mat-icon></button></div>
|
||||
<div *ngIf="playlists.audio.length === 0">
|
||||
No playlists available. Create one from your downloading audio files by clicking the blue plus button.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel class="big">
|
||||
<mat-expansion-panel (mouseleave)="accordionLeft('video')" (mouseenter)="accordionEntered('video')" class="big">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
Video
|
||||
@@ -123,23 +127,29 @@
|
||||
<div *ngIf="mp4s.length > 0;else nomp4s">
|
||||
<mat-grid-list style="margin-bottom: 15px;" (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"
|
||||
<app-file-card #videofilecard (removeFile)="removeFromMp4($event)" [title]="file.title" [name]="file.id" [thumbnailURL]="file.thumbnailURL"
|
||||
[length]="file.duration" [isAudio]="false"></app-file-card>
|
||||
<mat-progress-bar *ngIf="downloading_content['video'][file.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
<mat-divider *ngIf="playlists.video.length > 0"></mat-divider>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<div style="width: 100%; text-align: center; margin-top: 10px;" *ngIf="playlists.video.length > 0">
|
||||
<div style="width: 100%; text-align: center; margin-top: 10px;">
|
||||
<h6>Playlists</h6>
|
||||
</div>
|
||||
<mat-grid-list *ngIf="playlists.video.length > 0" (window:resize)="onResize($event)" [cols]="files_cols" rowHeight="150px">
|
||||
<mat-grid-tile *ngFor="let playlist of playlists.video; let i = index;">
|
||||
<app-file-card (removeFile)="removePlaylistMp4(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||
<app-file-card #videofilecard (removeFile)="removePlaylistMp4(playlist.id, i)" [title]="playlist.name" [name]="playlist.id" [thumbnailURL]="playlist_thumbnails[playlist.id]"
|
||||
[length]="null" [isAudio]="false" [isPlaylist]="true" [count]="playlist.fileNames.length"></app-file-card>
|
||||
<mat-progress-bar *ngIf="downloading_content['video'][playlist.id]" class="download-progress-bar" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
|
||||
<!-- Add video playlist button -->
|
||||
<div class="add-playlist-button"><button (click)="openCreatePlaylistDialog('video')" mat-fab><mat-icon>add</mat-icon></button></div>
|
||||
<div *ngIf="playlists.video.length === 0">
|
||||
No playlists available. Create one from your downloading video files by clicking the blue plus button.
|
||||
</div>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, ElementRef, ViewChild, ViewChildren, QueryList } 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 {MatSnackBar, MatDialog} from '@angular/material';
|
||||
import { saveAs } from 'file-saver';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/mapTo';
|
||||
@@ -16,6 +16,11 @@ import 'rxjs/add/operator/do'
|
||||
import 'rxjs/add/operator/switch'
|
||||
import { YoutubeSearchService, Result } from '../youtube-search.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component';
|
||||
import { Platform } from '@angular/cdk/platform';
|
||||
|
||||
export let audioFilesMouseHovering = false;
|
||||
export let videoFilesMouseHovering = false;
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -156,11 +161,13 @@ export class MainComponent implements OnInit {
|
||||
formats_loading = false;
|
||||
|
||||
@ViewChild('urlinput', { read: ElementRef, static: false }) urlInput: ElementRef;
|
||||
@ViewChildren('audiofilecard') audioFileCards: QueryList<FileCardComponent>;
|
||||
@ViewChildren('videofilecard') videoFileCards: QueryList<FileCardComponent>;
|
||||
last_valid_url = '';
|
||||
last_url_check = 0;
|
||||
|
||||
constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
||||
private router: Router) {
|
||||
private router: Router, public dialog: MatDialog, private platform: Platform) {
|
||||
this.audioOnly = false;
|
||||
|
||||
|
||||
@@ -202,7 +209,8 @@ export class MainComponent implements OnInit {
|
||||
this.postsService.getMp3s().subscribe(result => {
|
||||
const mp3s = result['mp3s'];
|
||||
const playlists = result['playlists'];
|
||||
this.mp3s = mp3s;
|
||||
// if they are different
|
||||
if (JSON.stringify(this.mp3s) !== JSON.stringify(mp3s)) { this.mp3s = mp3s };
|
||||
this.playlists.audio = playlists;
|
||||
|
||||
// get thumbnail url by using first video. this is a temporary hack
|
||||
@@ -216,7 +224,7 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.playlist_thumbnails[playlist.id] = videoToExtractThumbnail.thumbnailURL;
|
||||
if (videoToExtractThumbnail) { this.playlist_thumbnails[playlist.id] = videoToExtractThumbnail.thumbnailURL; }
|
||||
}
|
||||
}, error => {
|
||||
console.log(error);
|
||||
@@ -227,7 +235,8 @@ export class MainComponent implements OnInit {
|
||||
this.postsService.getMp4s().subscribe(result => {
|
||||
const mp4s = result['mp4s'];
|
||||
const playlists = result['playlists'];
|
||||
this.mp4s = mp4s;
|
||||
// if they are different
|
||||
if (JSON.stringify(this.mp4s) !== JSON.stringify(mp4s)) { this.mp4s = mp4s };
|
||||
this.playlists.video = playlists;
|
||||
|
||||
// get thumbnail url by using first video. this is a temporary hack
|
||||
@@ -241,7 +250,7 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
this.playlist_thumbnails[playlist.id] = videoToExtractThumbnail.thumbnailURL;
|
||||
if (videoToExtractThumbnail) { this.playlist_thumbnails[playlist.id] = videoToExtractThumbnail.thumbnailURL; }
|
||||
}
|
||||
},
|
||||
error => {
|
||||
@@ -322,7 +331,8 @@ export class MainComponent implements OnInit {
|
||||
|
||||
// app initialization.
|
||||
ngOnInit() {
|
||||
this.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window['MSStream'];
|
||||
// this.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window['MSStream'];
|
||||
this.iOS = this.platform.IOS;
|
||||
|
||||
if (localStorage.getItem('audioOnly') !== null) {
|
||||
this.audioOnly = localStorage.getItem('audioOnly') === 'true';
|
||||
@@ -396,9 +406,9 @@ export class MainComponent implements OnInit {
|
||||
|
||||
let customQualityConfiguration = null;
|
||||
if (this.selectedQuality !== '') {
|
||||
const cachedFormatsExists = this.cachedAvailableFormats[this.url];
|
||||
const cachedFormatsExists = this.cachedAvailableFormats[this.url] && this.cachedAvailableFormats[this.url]['formats'];
|
||||
if (cachedFormatsExists) {
|
||||
const audio_formats = this.cachedAvailableFormats[this.url]['audio'];
|
||||
const audio_formats = this.cachedAvailableFormats[this.url]['formats']['audio'];
|
||||
customQualityConfiguration = audio_formats[this.selectedQuality]['format_id'];
|
||||
}
|
||||
}
|
||||
@@ -415,9 +425,9 @@ export class MainComponent implements OnInit {
|
||||
});
|
||||
} else {
|
||||
let customQualityConfiguration = null;
|
||||
const cachedFormatsExists = this.cachedAvailableFormats[this.url];
|
||||
const cachedFormatsExists = this.cachedAvailableFormats[this.url] && this.cachedAvailableFormats[this.url]['formats'];
|
||||
if (cachedFormatsExists) {
|
||||
const video_formats = this.cachedAvailableFormats[this.url]['video'];
|
||||
const video_formats = this.cachedAvailableFormats[this.url]['formats']['video'];
|
||||
if (video_formats['best_audio_format'] && this.selectedQuality !== '') {
|
||||
customQualityConfiguration = video_formats[this.selectedQuality]['format_id'] + '+' + video_formats['best_audio_format'];
|
||||
}
|
||||
@@ -524,7 +534,7 @@ export class MainComponent implements OnInit {
|
||||
// tslint:disable-next-line: max-line-length
|
||||
const youtubeStrRegex = /(?:http(?:s)?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user)\/))([^\?&\"'<> #]+)/;
|
||||
const reYT = new RegExp(youtubeStrRegex);
|
||||
const ytValid = reYT.test(str);
|
||||
const ytValid = true || reYT.test(str);
|
||||
if (valid && ytValid && Date.now() - this.last_url_check > 1000) {
|
||||
if (str !== this.last_valid_url && this.allowQualitySelect) {
|
||||
// get info
|
||||
@@ -543,18 +553,32 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
|
||||
getURLInfo(url) {
|
||||
if (!(this.cachedAvailableFormats[url])) {
|
||||
this.formats_loading = true;
|
||||
if (!this.cachedAvailableFormats[url]) {
|
||||
this.cachedAvailableFormats[url] = {};
|
||||
}
|
||||
if (!(this.cachedAvailableFormats[url] && this.cachedAvailableFormats[url]['formats'])) {
|
||||
this.cachedAvailableFormats[url]['formats_loading'] = true;
|
||||
this.postsService.getFileInfo([url], 'irrelevant', true).subscribe(res => {
|
||||
if (url === this.url) { this.formats_loading = false; }
|
||||
this.cachedAvailableFormats[url]['formats_loading'] = false;
|
||||
const infos = res['result'];
|
||||
if (!infos || !infos.formats) {
|
||||
this.errorFormats(url);
|
||||
return;
|
||||
}
|
||||
const parsed_infos = this.getAudioAndVideoFormats(infos.formats);
|
||||
const available_formats = {audio: parsed_infos[0], video: parsed_infos[1]};
|
||||
this.cachedAvailableFormats[url] = available_formats;
|
||||
this.cachedAvailableFormats[url]['formats'] = available_formats;
|
||||
}, err => {
|
||||
this.errorFormats(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
errorFormats(url) {
|
||||
this.cachedAvailableFormats[url]['formats_loading'] = false;
|
||||
console.error('Could not load formats for url ' + url);
|
||||
}
|
||||
|
||||
attachToInput() {
|
||||
Observable.fromEvent(this.urlInput.nativeElement, 'keyup')
|
||||
.map((e: any) => e.target.value) // extract the value of input
|
||||
@@ -653,5 +677,45 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
return best_audio_format_for_mp4;
|
||||
}
|
||||
}
|
||||
|
||||
accordionEntered(type) {
|
||||
if (type === 'audio') {
|
||||
audioFilesMouseHovering = true;
|
||||
this.audioFileCards.forEach(filecard => {
|
||||
filecard.onHoverResponse();
|
||||
});
|
||||
} else if (type === 'video') {
|
||||
videoFilesMouseHovering = true;
|
||||
this.videoFileCards.forEach(filecard => {
|
||||
filecard.onHoverResponse();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
accordionLeft(type) {
|
||||
if (type === 'audio') {
|
||||
audioFilesMouseHovering = false;
|
||||
} else if (type === 'video') {
|
||||
videoFilesMouseHovering = false;
|
||||
}
|
||||
}
|
||||
|
||||
// creating a playlist
|
||||
openCreatePlaylistDialog(type) {
|
||||
const dialogRef = this.dialog.open(CreatePlaylistComponent, {
|
||||
data: {
|
||||
filesToSelectFrom: (type === 'audio') ? this.mp3s : this.mp4s,
|
||||
type: type
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
if (type === 'audio') { this.getMp3s() };
|
||||
if (type === 'video') { this.getMp4s() };
|
||||
this.openSnackBar('Successfully created playlist!', '');
|
||||
} else if (result === false) {
|
||||
this.openSnackBar('ERROR: failed to create playlist!', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.video-player:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.audio-styles {
|
||||
height: 50px;
|
||||
background-color: transparent;
|
||||
@@ -58,4 +62,18 @@
|
||||
.save-icon {
|
||||
bottom: 1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.update-playlist-button-div {
|
||||
float: right;
|
||||
margin-right: 30px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.spinner-div {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
top: 8px;
|
||||
}
|
||||
@@ -8,13 +8,21 @@
|
||||
</vg-player>
|
||||
</div>
|
||||
<div class="col-12 my-2">
|
||||
<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">{{decodeURI(name)}}</mat-button-toggle>
|
||||
<mat-button-toggle-group cdkDropList [cdkDropListSortingDisabled]="!id" (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical name="videoSelect" aria-label="Video Select" #group="matButtonToggleGroup">
|
||||
<mat-button-toggle cdkDrag *ngFor="let playlist_item of playlist; let i = index" [checked]="currentItem.title === playlist_item.title" (click)="onClickPlaylistItem(playlist_item, i)" class="toggle-button" [value]="playlist_item.title">{{decodeURI(playlist_item.title)}}</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-playlist-button-div" *ngIf="id && playlistChanged()">
|
||||
<div class="spinner-div">
|
||||
<mat-spinner *ngIf="playlist_updating" [diameter]="25"></mat-spinner>
|
||||
</div>
|
||||
<button color="primary" [disabled]="playlist_updating" (click)="updatePlaylist()" mat-raised-button>Save changes <mat-icon>update</mat-icon></button>
|
||||
|
||||
</div>
|
||||
|
||||
<div *ngIf="playlist.length > 1">
|
||||
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||
<button *ngIf="!id" color="accent" class="favorite-button" color="primary" (click)="namePlaylistDialog()" mat-fab><mat-icon class="save-icon">favorite</mat-icon></button>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PostsService } from 'app/posts.services';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { MatDialog, MatSnackBar } from '@angular/material';
|
||||
import { InputDialogComponent } from 'app/input-dialog/input-dialog.component';
|
||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
|
||||
export interface IMedia {
|
||||
title: string;
|
||||
@@ -19,6 +20,8 @@ export interface IMedia {
|
||||
export class PlayerComponent implements OnInit {
|
||||
|
||||
playlist: Array<IMedia> = [];
|
||||
original_playlist: string = null;
|
||||
playlist_updating = false;
|
||||
|
||||
currentIndex = 0;
|
||||
currentItem: IMedia = null;
|
||||
@@ -82,6 +85,7 @@ export class PlayerComponent implements OnInit {
|
||||
this.playlist.push(mediaObject);
|
||||
}
|
||||
this.currentItem = this.playlist[this.currentIndex];
|
||||
this.original_playlist = JSON.stringify(this.playlist);
|
||||
});
|
||||
|
||||
// this.getFileInfos();
|
||||
@@ -122,11 +126,20 @@ export class PlayerComponent implements OnInit {
|
||||
}
|
||||
|
||||
getFileInfos() {
|
||||
this.postsService.getFileInfo(this.fileNames, this.type, false).subscribe(res => {
|
||||
const fileNames = this.getFileNames();
|
||||
this.postsService.getFileInfo(fileNames, this.type, false).subscribe(res => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
getFileNames() {
|
||||
const fileNames = [];
|
||||
for (let i = 0; i < this.playlist.length; i++) {
|
||||
fileNames.push(this.playlist[i].title);
|
||||
}
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
decodeURI(string) {
|
||||
return decodeURI(string);
|
||||
}
|
||||
@@ -179,7 +192,8 @@ export class PlayerComponent implements OnInit {
|
||||
|
||||
// Eventually do additional checks on name
|
||||
if (name) {
|
||||
this.postsService.createPlaylist(name, this.fileNames, this.type, null).subscribe(res => {
|
||||
const fileNames = this.getFileNames();
|
||||
this.postsService.createPlaylist(name, fileNames, this.type, null).subscribe(res => {
|
||||
if (res['success']) {
|
||||
dialogRef.close();
|
||||
const new_playlist = res['new_playlist'];
|
||||
@@ -208,6 +222,30 @@ export class PlayerComponent implements OnInit {
|
||||
this.router.navigateByUrl(this.router.url + ';id=' + playlistID);
|
||||
}
|
||||
|
||||
drop(event: CdkDragDrop<string[]>) {
|
||||
moveItemInArray(this.playlist, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
playlistChanged() {
|
||||
return JSON.stringify(this.playlist) !== this.original_playlist;
|
||||
}
|
||||
|
||||
updatePlaylist() {
|
||||
const fileNames = this.getFileNames();
|
||||
this.playlist_updating = true;
|
||||
this.postsService.updatePlaylist(this.id, fileNames, this.type).subscribe(res => {
|
||||
this.playlist_updating = false;
|
||||
if (res['success']) {
|
||||
const fileNamesEncoded = fileNames.join('|nvr|');
|
||||
this.router.navigate(['/player', {fileNames: fileNamesEncoded, type: 'video', id: this.id}]);
|
||||
this.openSnackBar('Successfully updated playlist.', '');
|
||||
this.original_playlist = JSON.stringify(this.playlist);
|
||||
} else {
|
||||
this.openSnackBar('ERROR: Failed to update playlist.', '');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// snackbar helper
|
||||
public openSnackBar(message: string, action: string) {
|
||||
this.snackBar.open(message, action, {
|
||||
|
||||
@@ -107,6 +107,12 @@ export class PostsService {
|
||||
thumbnailURL: thumbnailURL});
|
||||
}
|
||||
|
||||
updatePlaylist(playlistID, fileNames, type) {
|
||||
return this.http.post(this.path + 'updatePlaylist', {playlistID: playlistID,
|
||||
fileNames: fileNames,
|
||||
type: type});
|
||||
}
|
||||
|
||||
removePlaylist(playlistID, type) {
|
||||
return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type});
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
"Themes": {
|
||||
"default_theme": "default",
|
||||
"allow_theme_change": true
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/favicon.ico
BIN
src/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
Reference in New Issue
Block a user