Skip to content

Commit

Permalink
feat: added favorites for xtream live streams
Browse files Browse the repository at this point in the history
  • Loading branch information
KiPSOFT committed Oct 20, 2024
2 parents b84d807 + 39b45b0 commit dfc7146
Show file tree
Hide file tree
Showing 19 changed files with 301 additions and 7 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ preload.js
shared/*.js
playwright-report
.nx
server.js

# dependencies
/node_modules
Expand Down
14 changes: 13 additions & 1 deletion electron/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
PLAYLIST_UPDATE_RESPONSE,
SET_MPV_PLAYER_PATH,
SET_VLC_PLAYER_PATH,
SETTINGS_UPDATE,
STALKER_REQUEST,
STALKER_RESPONSE,
XTREAM_REQUEST,
Expand All @@ -40,6 +41,7 @@ import {
import { Playlist } from '../shared/playlist.interface';
import { createPlaylistObject } from '../shared/playlist.utils';
import { ParsedPlaylist } from '../src/typings.d';
import { Server } from './server';

const fs = require('fs');
const https = require('https');
Expand Down Expand Up @@ -88,7 +90,12 @@ export class Api {

mpv;

settings;

server;

constructor(store) {
this.server = new Server(this);
this.store = store;
this.mpv = this.createMpvInstance();
this.mpv
Expand Down Expand Up @@ -286,7 +293,11 @@ export class Api {
.on(SET_VLC_PLAYER_PATH, (_event, vlcPlayerPath) => {
console.log('... setting vlc player path', vlcPlayerPath);
store.set(VLC_PLAYER_PATH, vlcPlayerPath);
});
})
.on(SETTINGS_UPDATE, (_event, arg) => {
this.settings = arg;
this.server.updateSettings();
}) ;

// listeners for EPG events
ipcMain
Expand Down Expand Up @@ -625,4 +636,5 @@ export class Api {
}
});
}

}
85 changes: 85 additions & 0 deletions electron/remote-control/index.sqrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<html>
<head>
<title>{{it.title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
box-sizing: border-box;
}
header {
width: 100%;
background-color: #f8f9fa;
padding: 10px 0;
text-align: center;
}
header h1 {
margin: 0;
font-size: 24px;
}
.button-container {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
color: white;
}
.up-channel {
background-color: #007bff;
}
.down-channel {
background-color: #28a745;
}
footer {
margin-top: 30px;
text-align: center;
}
footer p {
font-size: 14px;
color: #6c757d;
}
</style>
<script>
// AJAX isteği yapmak için fetch API kullanımı
function callAjax(url) {
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('Success:', data);
alert('AJAX request successful! Check console for data.');
})
.catch((error) => {
console.error('There was a problem with your fetch operation:', error);
});
}
</script>
</head>
<body>
<header>
<h1>{{it.headerText}}</h1>
</header>

<div class="button-container">
<button class="up-channel" onclick="callAjax('{{it.upChannelUrl}}')">{{it.upChannelText}}</button>
<button class="down-channel" onclick="callAjax('{{it.downChannelUrl}}')">{{it.downChannelText}}</button>
</div>

<footer>
<p>{{it.footer}}</p>
</footer>
</body>
</html>
86 changes: 86 additions & 0 deletions electron/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { IncomingMessage, ServerResponse } from "http";
import { REMOTE_CONTROL_CHANGE_CHANNEL } from "../shared/ipc-commands";

const http = require('http');
const Sqrl = require('squirrelly');
const fs = require('fs');
const path = require('path');

export class Server {
/** Server instance */
server: any;
/** Api instance */
api: any;

/**
* Creates a new server instance
* @param port port number
* @param api application instance
*/
constructor(_api: any) {
this.api = _api;
this.server = http.createServer(this.requestListener.bind(this));
}

updateSettings() {
if (this.api.settings.remoteControl && !this.server.listening) {
this.server.listen(this.api.settings.remoteControlPort);
} else if (this.server.listening && !this.api.settings.remoteControl) {
this.server.close();
}
}

async getTranslation() {
const language = this.api.settings && this.api.settings.language ? this.api.settings.language : 'en';
const languageFileContent = await fs.promises.readFile(path.join(__dirname, `../src/assets/i18n/${language}.json`), 'utf-8');
return JSON.parse(languageFileContent);
}

getTranslationValue(key: string, translation: any, defaultValue: string) {
if (translation && translation.REMOTE_CONTROL) {
const translationValue = translation.REMOTE_CONTROL[key];
if (translationValue) {
return translationValue;
}
}
return defaultValue;
}

async requestListener(request: IncomingMessage, response: ServerResponse) {
const currentPage = request.url?.split('?')[0]?.split('/').splice(-1)[0];
const translation = await this.getTranslation();
switch (currentPage) {
case '': {
const indexFile = await fs.promises.readFile(path.join(__dirname, './remote-control/', 'index.sqrl'), 'utf-8');
const renderedHtml = Sqrl.render(indexFile, {
headerText: this.getTranslationValue('HEADER', translation, 'Remote Control'),
upChannelText: this.getTranslationValue('UP_CHANNEL', translation, 'Up Channel'),
downChannelText: this.getTranslationValue('DOWN_CHANNEL', translation, 'Down Channel'),
upChannelUrl: '/upChannel',
downChannelUrl: '/downChannel',
title: this.getTranslationValue('TITLE', translation, 'IPTVNator'),
footer: this.getTranslationValue('FOOTER', translation, 'IPTVNator')
});
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(renderedHtml);
break;
}
case 'upChannel': {
this.api.mainWindow.webContents.send(REMOTE_CONTROL_CHANGE_CHANNEL, { type: 'up' });
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end('Up Channel');
break;
}
case 'downChannel': {
this.api.mainWindow.webContents.send(REMOTE_CONTROL_CHANGE_CHANNEL, { type: 'down' });
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end('Down Channel');
break;
}
default:
response.writeHead(404, { 'Content-Type': 'text/html' });
response.end('404');
break;
}
}
}
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"node-mpv": "github:4gray/Node-MPV",
"rxjs": "7.8.1",
"semver": "7.5.2",
"squirrelly": "9.1.0",
"uuid": "9.0.0",
"video.js": "7.20.3",
"videojs-contrib-quality-levels": "2.2.0",
Expand Down
6 changes: 6 additions & 0 deletions shared/ipc-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ export const XTREAM_RESPONSE = 'XTREAM_RESPONSE';
// Stalker
export const STALKER_REQUEST = 'STALKER_REQUEST';
export const STALKER_RESPONSE = 'STALKER_RESPONSE';

// Settings
export const SETTINGS_UPDATE = 'SETTINGS_UPDATE';

// Remote Control
export const REMOTE_CONTROL_CHANGE_CHANNEL = 'REMOTE_CONTROL_CHANGE_CHANNEL';
4 changes: 2 additions & 2 deletions src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ describe('AppComponent', () => {

expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
expect(settingsService.changeTheme).toHaveBeenCalledWith(theme);
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(1);
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(2);
expect(translateService.use).toHaveBeenCalledWith(language);
});

Expand All @@ -245,7 +245,7 @@ describe('AppComponent', () => {

expect(spyOnSettingsGet).toHaveBeenCalledWith(STORE_KEY.Settings);
expect(settingsService.changeTheme).toHaveBeenCalledWith(theme);
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(0);
expect(electronService.sendIpcEvent).toHaveBeenCalledTimes(1);
expect(translateService.use).toHaveBeenCalledWith(defaultLanguage);
});
});
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EPG_FETCH_DONE,
ERROR,
OPEN_FILE,
SETTINGS_UPDATE,
SHOW_WHATS_NEW,
VIEW_ADD_PLAYLIST,
VIEW_SETTINGS,
Expand Down Expand Up @@ -148,6 +149,7 @@ export class AppComponent {
.getValueFromLocalStorage(STORE_KEY.Settings)
.subscribe((settings: Settings) => {
if (settings && Object.keys(settings).length > 0) {
this.electronService.sendIpcEvent(SETTINGS_UPDATE, settings);
this.translate.use(settings.language ?? this.DEFAULT_LANG);
if (
settings.epgUrl?.length > 0 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
trackBy: trackBy;
templateCacheSize: 0
"
[selected]="item.stream_id === activeLiveStream?.stream_id"
[value]="item"
(click)="itemClicked.emit(item)"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class LiveStreamLayoutComponent {
@Input({ required: true }) player: VideoPlayer = VideoPlayer.VideoJs;
@Input() epgItems: EpgItem[];
@Input() streamUrl: string;
@Input() activeLiveStream: XtreamItem;

@Output() itemClicked = new EventEmitter<XtreamItem>();

Expand Down
30 changes: 30 additions & 0 deletions src/app/settings/settings.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,36 @@
</div>
</div>
<mat-divider></mat-divider>
<div class="row">
<div class="column">
{{ 'SETTINGS.REMOTE_CONTROL' | translate }}
<p>{{ 'SETTINGS.REMOTE_CONTROL_DESCRIPTION' | translate }}</p>
</div>
<div class="column" style="margin-right: 10px">
<mat-checkbox
class="column"
formControlName="remoteControl"
></mat-checkbox>
</div>
</div>
<mat-divider></mat-divider>
<div class="row" *ngIf="settingsForm.value.remoteControl === true">
<div class="column">
{{ 'SETTINGS.REMOTE_CONTROL_PORT' | translate }}
<p>{{ 'SETTINGS.REMOTE_CONTROL_PORT_DESCRIPTION' | translate }}</p>
</div>
<div class="column" style="margin-right: 10px">
<mat-form-field appearance="outline" class="full-width">
<input
matInput
type="text"
id="remoteControlPort"
formControlName="remoteControlPort"
/>
</mat-form-field>
</div>
</div>
<mat-divider></mat-divider>
<div class="row">
<div class="column">
{{ 'SETTINGS.VERSION' | translate }}
Expand Down
2 changes: 2 additions & 0 deletions src/app/settings/settings.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const DEFAULT_SETTINGS = {
theme: Theme.LightTheme,
mpvPlayerPath: '',
vlcPlayerPath: '',
remoteControl: false,
remoteControlPort: 3000
};

describe('SettingsComponent', () => {
Expand Down
Loading

0 comments on commit dfc7146

Please sign in to comment.