Merge pull request #55 from antebrl/50-reload-playlists-iteratively
50 reload playlists iteratively
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
class Channel {
|
||||
static nextId = 0;
|
||||
constructor(name, url, avatar, mode, headers = [], group = null, playlist = null, playlistName = null) {
|
||||
constructor(name, url, avatar, mode, headers = [], group = null, playlist = null, playlistName = null, playlistUpdate = false) {
|
||||
this.id = Channel.nextId++;
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
@@ -11,6 +11,7 @@ class Channel {
|
||||
this.group = group;
|
||||
this.playlist = playlist;
|
||||
this.playlistName = playlistName;
|
||||
this.playlistUpdate = playlistUpdate;
|
||||
}
|
||||
|
||||
restream() {
|
||||
|
||||
16
backend/models/Playlist.js
Normal file
16
backend/models/Playlist.js
Normal file
@@ -0,0 +1,16 @@
|
||||
class Playlist {
|
||||
static nextId = 0;
|
||||
constructor(playlist, playlistName, mode, playlistUpdate, headers = []) {
|
||||
this.headers = headers;
|
||||
this.mode = mode;
|
||||
this.playlist = playlist;
|
||||
this.playlistName = playlistName;
|
||||
this.playlistUpdate = playlistUpdate;
|
||||
}
|
||||
|
||||
static from(json){
|
||||
return Object.assign(new Playlist(), json);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Playlist;
|
||||
20
backend/package-lock.json
generated
20
backend/package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
"iptv-playlist-parser": "^0.13.0",
|
||||
"node-cron": "^3.0.3",
|
||||
"request": "^2.88.2",
|
||||
"socket.io": "^4.8.1"
|
||||
}
|
||||
@@ -868,6 +869,25 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-cron": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
||||
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
|
||||
"dependencies": {
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-cron/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
"iptv-playlist-parser": "^0.13.0",
|
||||
"node-cron": "^3.0.3",
|
||||
"request": "^2.88.2",
|
||||
"socket.io": "^4.8.1"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ const channelController = require('./controllers/ChannelController');
|
||||
const streamController = require('./services/restream/StreamController');
|
||||
const ChannelService = require('./services/ChannelService');
|
||||
const PlaylistSocketHandler = require('./socket/PlaylistSocketHandler');
|
||||
const PlaylistUpdater = require('./services/PlaylistUpdater');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -41,6 +42,8 @@ const server = app.listen(PORT, async () => {
|
||||
if (ChannelService.getCurrentChannel().restream()) {
|
||||
await streamController.start(ChannelService.getCurrentChannel());
|
||||
}
|
||||
PlaylistUpdater.startScheduler();
|
||||
PlaylistUpdater.registerChannelsPlaylist(ChannelService.getChannels());
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class ChannelService {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
addChannel({ name, url, avatar, mode, headersJson, group = null, playlist = null, playlistName = null }, save = true) {
|
||||
addChannel({ name, url, avatar, mode, headersJson, group = null, playlist = null, playlistName = null, playlistUpdate = false }, save = true) {
|
||||
// const existing = this.channels.find(channel => channel.url === url);
|
||||
// if (existing) {
|
||||
// throw new Error('Channel already exists');
|
||||
@@ -42,7 +42,7 @@ class ChannelService {
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
const newChannel = new Channel(name, url, avatar, mode, headers, group, playlist, playlistName);
|
||||
const newChannel = new Channel(name, url, avatar, mode, headers, group, playlist, playlistName, playlistUpdate);
|
||||
this.channels.push(newChannel);
|
||||
if(save) ChannelStorage.save(this.channels);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = {
|
||||
{ "key": "Referer", "value": "https://embedme.top/" }
|
||||
];
|
||||
|
||||
const channels = [
|
||||
const defaultChannels = [
|
||||
//Some Test-channels to get started, remove this when using your own playlist
|
||||
//new Channel('Das Erste', "https://mcdn.daserste.de/daserste/de/master.m3u8", "https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Das_Erste-Logo_klein.svg/768px-Das_Erste-Logo_klein.svg.png", 'direct'),
|
||||
new Channel('DAZN 1 DE', "https://xyzdddd.mizhls.ru/lb/premium426/index.m3u8", "https://upload.wikimedia.org/wikipedia/commons/4/49/DAZN_1.svg", 'proxy', daddyHeaders),
|
||||
@@ -41,11 +41,11 @@ module.exports = {
|
||||
return channelsJson.map(channelJson => Channel.from(channelJson));
|
||||
} catch (err) {
|
||||
console.error('Error loading data from storage:', err);
|
||||
return channels;
|
||||
return defaultChannels;
|
||||
}
|
||||
}
|
||||
this.save(channels);
|
||||
return channels;
|
||||
this.save(defaultChannels);
|
||||
return defaultChannels;
|
||||
},
|
||||
|
||||
save(data) {
|
||||
|
||||
@@ -5,7 +5,7 @@ const StreamedSuSession = require('./session/StreamedSuSession');
|
||||
|
||||
class PlaylistService {
|
||||
|
||||
async addPlaylist(playlist, playlistName, mode, headersJson) {
|
||||
async addPlaylist(playlist, playlistName, mode, playlistUpdate, headersJson) {
|
||||
|
||||
console.log('Adding playlist', playlist);
|
||||
|
||||
@@ -43,7 +43,8 @@ class PlaylistService {
|
||||
headersJson: headersJson,
|
||||
group: channel.group.title,
|
||||
playlist: playlist,
|
||||
playlistName: playlistName
|
||||
playlistName: playlistName,
|
||||
playlistUpdate: playlistUpdate
|
||||
}, false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
73
backend/services/PlaylistUpdater.js
Normal file
73
backend/services/PlaylistUpdater.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const cron = require('node-cron');
|
||||
const PlaylistService = require('./PlaylistService');
|
||||
const Playlist = require('../models/Playlist');
|
||||
|
||||
class PlaylistUpdater {
|
||||
constructor() {
|
||||
this.playlists = new Map();
|
||||
}
|
||||
|
||||
#contains(playlistUrl) {
|
||||
return this.playlists.has(playlistUrl);
|
||||
}
|
||||
|
||||
register(playlist) {
|
||||
if (this.#contains(playlist.playlist)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Registering playlist:', playlist.playlist);
|
||||
this.playlists.set(playlist.playlist, playlist);
|
||||
}
|
||||
|
||||
registerChannelsPlaylist(channels) {
|
||||
for (const channel of channels) {
|
||||
if (channel.playlist && channel.playlistUpdate) {
|
||||
this.register(
|
||||
new Playlist(
|
||||
channel.playlist,
|
||||
channel.playlistName,
|
||||
channel.mode,
|
||||
channel.playlistUpdate,
|
||||
channel.headers
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(playlistUrl) {
|
||||
if (this.#contains(playlistUrl)) {
|
||||
this.playlists.delete(playlistUrl);
|
||||
console.log(`Deleted playlist with URL: ${playlistUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
startScheduler() {
|
||||
// Cron-Job: "0 3 * * *" -> Every day at 3:00 AM
|
||||
cron.schedule('0 3 * * *', () => {
|
||||
this.updatePlaylists();
|
||||
});
|
||||
}
|
||||
|
||||
updatePlaylists() {
|
||||
console.log('Updating playlists at:', new Date());
|
||||
this.playlists.forEach(async (playlist) => {
|
||||
try {
|
||||
// Fetch and renew playlist
|
||||
await PlaylistService.deletePlaylist(playlist.playlist);
|
||||
await PlaylistService.addPlaylist(
|
||||
playlist.playlist,
|
||||
playlist.playlistName,
|
||||
playlist.mode,
|
||||
playlist.playlistUpdate,
|
||||
playlist.headers
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error while updating playlist ${playlist.playlistName}:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PlaylistUpdater();
|
||||
@@ -1,16 +1,22 @@
|
||||
const PlaylistService = require('../services/PlaylistService');
|
||||
const ChannelService = require('../services/ChannelService');
|
||||
const Channel = require('../models/Channel');
|
||||
const PlaylistUpdater = require('../services/PlaylistUpdater');
|
||||
const Playlist = require('../models/Playlist');
|
||||
|
||||
async function handleAddPlaylist({ playlist, playlistName, mode, headers }, io, socket) {
|
||||
async function handleAddPlaylist({ playlist, playlistName, mode, playlistUpdate, headers }, io, socket) {
|
||||
try {
|
||||
const channels = await PlaylistService.addPlaylist(playlist, playlistName, mode, headers);
|
||||
const channels = await PlaylistService.addPlaylist(playlist, playlistName, mode, playlistUpdate, headers);
|
||||
|
||||
if (channels) {
|
||||
channels.forEach(channel => {
|
||||
io.emit('channel-added', channel);
|
||||
});
|
||||
}
|
||||
|
||||
if(playlistUpdate) {
|
||||
PlaylistUpdater.register(new Playlist(playlist, playlistName, mode, playlistUpdate, headers));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
socket.emit('app-error', { message: err.message });
|
||||
@@ -31,6 +37,12 @@ async function handleUpdatePlaylist({ playlist, updatedAttributes }, io, socket)
|
||||
channels.forEach(channel => {
|
||||
io.emit('channel-updated', channel);
|
||||
});
|
||||
|
||||
PlaylistUpdater.delete(playlist);
|
||||
if(updatedAttributes.playlistUpdate) {
|
||||
PlaylistUpdater.register(new Playlist(playlist, updatedAttributes.playlistName, updatedAttributes.mode, updatedAttributes.playlistUpdate, updatedAttributes.headers));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
socket.emit('app-error', { message: err.message });
|
||||
@@ -45,6 +57,9 @@ async function handleDeletePlaylist(playlist, io, socket) {
|
||||
io.emit('channel-deleted', channel.id);
|
||||
});
|
||||
io.emit('channel-selected', ChannelService.getCurrentChannel());
|
||||
|
||||
PlaylistUpdater.delete(playlist);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
socket.emit('app-error', { message: err.message });
|
||||
|
||||
@@ -12,7 +12,7 @@ interface ChannelModalProps {
|
||||
}
|
||||
|
||||
function ChannelModal({ onClose, channel }: ChannelModalProps) {
|
||||
const [type, setType] = useState<'channel' | 'playlist'>('channel');
|
||||
const [type, setType] = useState<'channel' | 'playlist'>('playlist');
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [inputMethod, setInputMethod] = useState<'url' | 'text'>('url');
|
||||
|
||||
@@ -25,6 +25,7 @@ function ChannelModal({ onClose, channel }: ChannelModalProps) {
|
||||
const [playlistName, setPlaylistName] = useState('');
|
||||
const [playlistUrl, setPlaylistUrl] = useState('');
|
||||
const [playlistText, setPlaylistText] = useState('');
|
||||
const [playlistUpdate, setPlaylistUpdate] = useState(false);
|
||||
|
||||
const { addToast } = useContext(ToastContext);
|
||||
|
||||
@@ -36,6 +37,7 @@ function ChannelModal({ onClose, channel }: ChannelModalProps) {
|
||||
setMode(channel.mode);
|
||||
setHeaders(channel.headers);
|
||||
setPlaylistName(channel.playlistName);
|
||||
setPlaylistUpdate(channel.playlistUpdate);
|
||||
setIsEditMode(true);
|
||||
setType('channel');
|
||||
|
||||
@@ -62,8 +64,9 @@ function ChannelModal({ onClose, channel }: ChannelModalProps) {
|
||||
setPlaylistName('');
|
||||
setPlaylistUrl('');
|
||||
setPlaylistText('');
|
||||
setPlaylistUpdate(false);
|
||||
setIsEditMode(false);
|
||||
setType('channel');
|
||||
setType('playlist');
|
||||
setInputMethod('url');
|
||||
}
|
||||
}, [channel]);
|
||||
@@ -107,6 +110,7 @@ function ChannelModal({ onClose, channel }: ChannelModalProps) {
|
||||
inputMethod === 'url' ? playlistUrl.trim() : playlistText.trim(),
|
||||
playlistName.trim(),
|
||||
mode,
|
||||
playlistUpdate,
|
||||
JSON.stringify(headers)
|
||||
);
|
||||
}
|
||||
@@ -134,6 +138,7 @@ function ChannelModal({ onClose, channel }: ChannelModalProps) {
|
||||
socketService.updatePlaylist(channel!.playlist, {
|
||||
playlist: newPlaylist,
|
||||
playlistName: playlistName.trim(),
|
||||
playlistUpdate: playlistUpdate,
|
||||
mode: mode,
|
||||
headers: headers,
|
||||
});
|
||||
@@ -411,6 +416,23 @@ function ChannelModal({ onClose, channel }: ChannelModalProps) {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Playlist auto-update toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<label className="block text-sm font-medium">Playlist Auto Update</label>
|
||||
<p className="text-sm text-gray-400">Automatically update playlist once a day</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={playlistUpdate}
|
||||
onChange={(e) => setPlaylistUpdate(e.target.checked)}
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -98,10 +98,10 @@ class SocketService {
|
||||
}
|
||||
|
||||
// Add playlist
|
||||
addPlaylist(playlist: string, playlistName: string, mode: ChannelMode, headers: string) {
|
||||
addPlaylist(playlist: string, playlistName: string, mode: ChannelMode, playlistUpdate: boolean, headers: string) {
|
||||
if (!this.socket) throw new Error('Socket is not connected.');
|
||||
|
||||
this.socket.emit('add-playlist', { playlist, playlistName, mode, headers });
|
||||
this.socket.emit('add-playlist', { playlist, playlistName, mode, playlistUpdate, headers });
|
||||
}
|
||||
|
||||
// Update playlist
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface Channel {
|
||||
group: string;
|
||||
playlist: string;
|
||||
playlistName: string;
|
||||
playlistUpdate: boolean;
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
|
||||
Reference in New Issue
Block a user