refactor: switch to iptv-playlist-parser

This commit is contained in:
4gray committed 2020-07-03 08:55:51 +02:00
1 parent 4dabc85c6b
commit 19ef7e18bf
9 files changed
+97 -69

No files matched your search

+30 -11
View File
@@ -7426,6 +7426,9 @@
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"dev": true
},
"iptv-playlist-parser": {
"version": "git+https://github.com/4gray/iptv-playlist-parser.git#519d10a58128496e246f24bc2cc36dd0ec288c6b"
},
"is-absolute-url": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
@@ -9014,11 +9017,6 @@
}
}
},
"m3u8-file-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/m3u8-file-parser/-/m3u8-file-parser-0.2.3.tgz",
"integrity": "sha512-Iz5aPr9J9PYBKyKK8+HYlg4Oqcwcg+TsxfoKVD1Mn0OqWES4Y82Na61XgMeLmKCujz+lxS/ygCNEdodgRLiOiQ=="
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
@@ -12168,7 +12166,14 @@
"safe-buffer": "5.1.2",
"tough-cookie": "2.4.3",
"tunnel-agent": "0.6.0",
"uuid": "3.3.3"
"uuid": "3.4.0"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
}
}
},
"require-directory": {
@@ -14324,7 +14329,7 @@
"requires": {
"debug": "3.2.6",
"request": "2.88.0",
"uuid": "3.3.3"
"uuid": "3.4.0"
},
"dependencies": {
"debug": {
@@ -14335,6 +14340,12 @@
"requires": {
"ms": "2.1.2"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
@@ -14526,9 +14537,9 @@
"dev": true
},
"uuid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz",
"integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q=="
},
"validate-npm-package-license": {
"version": "3.0.4",
@@ -15183,7 +15194,15 @@
"dev": true,
"requires": {
"ansi-colors": "3.2.4",
"uuid": "3.3.3"
"uuid": "3.4.0"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"webpack-merge": {
+2 -1
View File
@@ -26,13 +26,14 @@
"@angular/service-worker": "~9.1.11",
"@datorama/akita": "^4.23.2",
"hls.js": "^0.12.4",
"m3u8-file-parser": "^0.2.3",
"iptv-playlist-parser": "git+https://github.com/4gray/iptv-playlist-parser.git",
"material-icons": "^0.3.1",
"ngx-filter-pipe": "^2.1.2",
"ngx-indexed-db": "^5.0.4",
"ngx-uploader": "^7.1.0",
"rxjs": "~6.5.5",
"tslib": "^1.13.0",
"uuid": "^8.2.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
@@ -1,14 +1,17 @@
<mat-list id="top-panel">
<mat-list-item>
<mat-form-field class="full-width">
<input matInput placeholder="Search channel {{channelList?.length}}" [(ngModel)]="searchTerm.inf.title">
<input matInput placeholder="Search channel ({{channelList?.length}})" [(ngModel)]="searchTerm.name">
</mat-form-field>
<button mat-icon-button [routerLink]="'/'" matTooltip="Upload or select other playlist"><mat-icon>create_new_folder</mat-icon></button>
<button mat-icon-button [routerLink]="'/'" matTooltip="Upload or select other playlist">
<mat-icon>create_new_folder</mat-icon>
</button>
</mat-list-item>
</mat-list>
<mat-nav-list id="channels-list">
<mat-list-item *ngFor=" let channel of channelList | filterBy: searchTerm; index as i" [class.active]="selected?.id === channel.id" (click)="selectChannel(channel)">
{{i+1 + '. ' + channel?.inf?.title || 'Unnamed Channel'}}
<mat-list-item *ngFor=" let channel of channelList | filterBy: searchTerm; index as i"
[class.active]="selected?.id === channel.id" (click)="selectChannel(channel)">
{{i+1 + '. ' + channel?.name || 'Unnamed Channel'}}
<mat-divider></mat-divider>
</mat-list-item>
</mat-nav-list>
@@ -22,16 +22,14 @@ export class ChannelListContainerComponent {
*/
@Output() changeChannel: EventEmitter<{
url: string;
title: string;
name: string;
}> = new EventEmitter();
/**
* Search term for channel filter
*/
searchTerm: any = {
inf: {
title: '',
},
name: '',
};
/**
@@ -40,6 +38,6 @@ export class ChannelListContainerComponent {
*/
selectChannel(channel: Channel): void {
this.selected = channel;
this.changeChannel.emit({ url: channel.url, title: channel.inf.title});
this.changeChannel.emit({ url: channel.url, name: channel.name });
}
}
@@ -6,7 +6,7 @@
</mat-card>
<br />
<mat-card>
<mat-card-subtitle>Add playlist</mat-card-subtitle>
<mat-card-subtitle>Add playlist via file upload</mat-card-subtitle>
<div class="drop-container" ngFileDrop [options]="options" (uploadOutput)="onUploadOutput($event)"
[uploadInput]="uploadInput">
<div>
@@ -6,12 +6,12 @@ import {
humanizeBytes,
UploaderOptions,
} from 'ngx-uploader';
import { ChannelStore, createChannel } from 'src/app/state';
import { ChannelStore, createChannel, Channel } from 'src/app/state';
import { M3uService } from 'src/app/services/m3u-service.service';
import { Router } from '@angular/router';
import { NgxIndexedDBService } from 'ngx-indexed-db';
import { PLAYLISTS_STORE } from 'src/app/db.config';
import {MatSnackBar} from '@angular/material/snack-bar';
import { MatSnackBar } from '@angular/material/snack-bar';
/**
* Describes playlist interface
@@ -74,18 +74,13 @@ export class PlaylistUploaderComponent {
this.isLoading = true;
if (this.files.length > 0) {
const fileReader = new FileReader();
fileReader.onload = fileLoadedEvent => {
fileReader.onload = (fileLoadedEvent) => {
const result = (fileLoadedEvent.target as FileReader)
.result;
const array = (result as string).split('\n');
const playlist = this.m3uService.convertArrayToPlaylist(
array
);
this.savePlaylist(
this.files[0].name,
playlist
);
const playlist = this.m3uService.parsePlaylist(array);
this.savePlaylist(this.files[0].name, playlist);
this.setPlaylist(playlist);
};
@@ -101,7 +96,7 @@ export class PlaylistUploaderComponent {
typeof output.file !== 'undefined'
) {
const index = this.files.findIndex(
file =>
(file) =>
typeof output.file !== 'undefined' &&
file.id === output.file.id
);
@@ -138,9 +133,17 @@ export class PlaylistUploaderComponent {
* @param playlist playlist to save
*/
savePlaylist(name: string, playlist: any): void {
this.dbService.add<Playlist>('playlists', { filename: name, title: name, playlist, importDate: new Date().getMilliseconds(), lastUsage: new Date().getMilliseconds() }).then(() => {
console.log('playlist saved!')
});
this.dbService
.add<Playlist>('playlists', {
filename: name,
title: name,
playlist,
importDate: new Date().getMilliseconds(),
lastUsage: new Date().getMilliseconds(),
})
.then(() => {
console.log('playlist saved!');
});
}
/**
@@ -148,8 +151,8 @@ export class PlaylistUploaderComponent {
*/
getPlaylists(): void {
this.dbService.getAll('playlists').then(
(playlists: Playlist[]) => this.playlists = playlists,
error => {
(playlists: Playlist[]) => (this.playlists = playlists),
(error) => {
console.error(error);
}
);
@@ -158,10 +161,13 @@ export class PlaylistUploaderComponent {
/**
* Sets the given playlist as active for the current session
* @param playlist m3u playlist
* // TODO: use typings from iptv-parser lib
*/
setPlaylist(playlist: any): void {
this.channelStore.reset();
const channels = playlist.segments.map(element => createChannel(element));
setPlaylist(playlist: { header: any; items: Channel[] }): void {
this.channelStore.reset();
const channels = playlist.items.map((element) =>
createChannel(element)
);
this.channelStore.upsertMany(channels);
this.navigateToPlayer();
}
@@ -170,9 +176,11 @@ export class PlaylistUploaderComponent {
* Removes the provided playlist from the indexedDb
* @param playlist playlist to remove
*/
removePlaylist(playlist: Playlist) {
removePlaylist(playlist: Playlist): void {
this.dbService.delete(PLAYLISTS_STORE, playlist.id);
this.getPlaylists();
this.snackBar.open('Done! Playlist was removed.', null,{ duration: 2000 });
this.snackBar.open('Done! Playlist was removed.', null, {
duration: 2000,
});
}
}
@@ -1,13 +1,14 @@
<mat-nav-list>
<div mat-subheader>Recently added playlists</div>
<div mat-subheader>Recently added playlists ({{playlists.length}})</div>
<a mat-list-item *ngFor="let item of playlists; last as last" (click)="playlistClicked.emit(item.playlist);">
<mat-icon mat-list-icon>folder</mat-icon>
<div mat-line>{{ item.title || item.filename }}</div>
<div mat-line>Channels: {{ item.playlist?.segments.length }}</div>
<div mat-line>Channels: {{ item.playlist?.items?.length }}</div>
<!--<button mat-icon-button color="primary" (click)="$event.stopPropagation(); renameClicked.emit(item)" matTooltip="Rename playlist">
<mat-icon>edit</mat-icon>
</button>-->
<button mat-icon-button color="primary" (click)="$event.stopPropagation(); removeClicked.emit(item)" matTooltip="Remove playlist">
<button mat-icon-button color="primary" (click)="$event.stopPropagation(); removeClicked.emit(item)"
matTooltip="Remove playlist">
<mat-icon>delete</mat-icon>
</button>
<mat-divider *ngIf="!last"></mat-divider>
+5 -19
View File
@@ -1,30 +1,16 @@
import { Injectable } from '@angular/core';
import * as M3U8FileParser from 'm3u8-file-parser';
import { parse } from 'iptv-playlist-parser';
@Injectable({
providedIn: 'root',
})
export class M3uService {
/**
* m3u8 parser
*/
m3u8FileParser = new M3U8FileParser();
/**
* Converts string based array to playlist object
* Parses string based array to playlist object
* @param m3uArray m3u playlist as array with strings
*/
convertArrayToPlaylist(m3uArray: any[]): any {
this.m3u8FileParser.read(m3uArray.join('\n'));
return this.m3u8FileParser.getResult();
}
/**
* Converts string to playlist structure
* @param m3uString playlist as string
*/
convertStringToPlaylist(m3uString: string): any {
this.m3u8FileParser.read(m3uString);
return this.m3u8FileParser.getResult();
parsePlaylist(m3uArray: any[]): any {
const playlistAsString = m3uArray.join('\n');
return parse(playlistAsString);
}
}
+16 -4
View File
@@ -1,15 +1,27 @@
import { ID, guid } from '@datorama/akita';
/**
* Represents channel object
* TODO: define channel interface in iptv-parser library
*/
export interface Channel {
id: ID;
inf: any;
id: string;
url: string;
name: string;
group: {
title: string;
};
}
/**
* Creates new channel object based on the given fields
* @param params partial channel object
*/
export function createChannel(params: Partial<Channel>) {
return {
id: guid(),
inf: params.inf,
url: params.url
name: params.name,
group: params.group,
url: params.url,
} as Channel;
}