Add m3u playlist support
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
const ChannelService = require('../services/ChannelService');
|
||||
const fs = require('fs');
|
||||
const m3uParser = require('m3u8-parser');
|
||||
|
||||
module.exports = {
|
||||
getChannels(req, res) {
|
||||
@@ -38,4 +40,39 @@ module.exports = {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
addPlaylist(req, res) {
|
||||
try {
|
||||
const { playlistUrl } = req.body;
|
||||
const playlistContent = fs.readFileSync(playlistUrl, 'utf8');
|
||||
const parser = new m3uParser.Parser();
|
||||
parser.push(playlistContent);
|
||||
parser.end();
|
||||
|
||||
const parsedPlaylist = parser.manifest;
|
||||
const channels = parsedPlaylist.segments.map(segment => ({
|
||||
name: segment.title,
|
||||
url: segment.uri,
|
||||
avatar: '',
|
||||
restream: false,
|
||||
headersJson: '[]'
|
||||
}));
|
||||
|
||||
channels.forEach(channel => {
|
||||
ChannelService.addChannel(channel.name, channel.url, channel.avatar, channel.restream, channel.headersJson);
|
||||
});
|
||||
|
||||
res.status(201).json({ message: 'Playlist added successfully' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ChannelController = require('../controllers/ChannelController');
|
||||
|
||||
router.post('/playlist', ChannelController.addPlaylist);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const streamController = require('./streaming/StreamController');
|
||||
const Channel = require('../models/Channel');
|
||||
const fs = require('fs');
|
||||
const m3uParser = require('@pawanpaudel93/m3u-parse');
|
||||
|
||||
class ChannelService {
|
||||
constructor() {
|
||||
@@ -112,6 +114,28 @@ class ChannelService {
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
addChannelsFromPlaylist(playlistUrl) {
|
||||
const playlistContent = fs.readFileSync(playlistUrl, 'utf8');
|
||||
const parser = new m3uParser.Parser();
|
||||
parser.push(playlistContent);
|
||||
parser.end();
|
||||
|
||||
const parsedPlaylist = parser.manifest;
|
||||
const channels = parsedPlaylist.segments.map(segment => ({
|
||||
name: segment.title,
|
||||
url: segment.uri,
|
||||
avatar: '',
|
||||
restream: false,
|
||||
headersJson: '[]'
|
||||
}));
|
||||
|
||||
channels.forEach(channel => {
|
||||
this.addChannel(channel.name, channel.url, channel.avatar, channel.restream, channel.headersJson);
|
||||
});
|
||||
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ChannelService();
|
||||
|
||||
@@ -42,4 +42,27 @@ module.exports = (io, socket) => {
|
||||
socket.emit('app-error', { message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('upload-playlist', async (data) => {
|
||||
try {
|
||||
let channels;
|
||||
if (data.playlistUrl) {
|
||||
channels = await ChannelService.addChannelsFromPlaylist(data.playlistUrl);
|
||||
} else if (data instanceof FormData) {
|
||||
const playlistFile = data.get('playlistFile');
|
||||
if (playlistFile) {
|
||||
const playlistContent = await playlistFile.text();
|
||||
channels = await ChannelService.addChannelsFromPlaylist(playlistContent);
|
||||
}
|
||||
}
|
||||
if (channels) {
|
||||
channels.forEach(channel => {
|
||||
io.emit('channel-added', channel);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
socket.emit('app-error', { message: err.message });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,6 +17,8 @@ function ChannelModal({ isOpen, onClose, channel }: ChannelModalProps) {
|
||||
const [restream, setRestream] = useState(false);
|
||||
const [headers, setHeaders] = useState<CustomHeader[]>([]);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [playlistUrl, setPlaylistUrl] = useState('');
|
||||
const [playlistFile, setPlaylistFile] = useState<File | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (channel) {
|
||||
@@ -36,7 +38,7 @@ function ChannelModal({ isOpen, onClose, channel }: ChannelModalProps) {
|
||||
}
|
||||
}, [channel]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!name.trim() || !url.trim()) return;
|
||||
|
||||
@@ -52,6 +54,16 @@ function ChannelModal({ isOpen, onClose, channel }: ChannelModalProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (playlistUrl.trim()) {
|
||||
socketService.uploadPlaylist({ playlistUrl: playlistUrl.trim() });
|
||||
}
|
||||
|
||||
if (playlistFile) {
|
||||
const formData = new FormData();
|
||||
formData.append('playlistFile', playlistFile);
|
||||
socketService.uploadPlaylist(formData);
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -87,6 +99,16 @@ function ChannelModal({ isOpen, onClose, channel }: ChannelModalProps) {
|
||||
setHeaders(newHeaders);
|
||||
};
|
||||
|
||||
const handlePlaylistUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPlaylistUrl(e.target.value);
|
||||
};
|
||||
|
||||
const handlePlaylistFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
setPlaylistFile(e.target.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
@@ -209,6 +231,33 @@ function ChannelModal({ isOpen, onClose, channel }: ChannelModalProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="playlistUrl" className="block text-sm font-medium mb-1">
|
||||
M3U Playlist URL
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="playlistUrl"
|
||||
value={playlistUrl}
|
||||
onChange={handlePlaylistUrlChange}
|
||||
className="w-full bg-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter M3U playlist URL"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="playlistFile" className="block text-sm font-medium mb-1">
|
||||
M3U Playlist File
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="playlistFile"
|
||||
onChange={handlePlaylistFileChange}
|
||||
className="w-full bg-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
accept=".m3u"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3">
|
||||
{isEditMode && (
|
||||
<button
|
||||
@@ -239,4 +288,4 @@ function ChannelModal({ isOpen, onClose, channel }: ChannelModalProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelModal;
|
||||
export default ChannelModal;
|
||||
|
||||
@@ -36,6 +36,25 @@ const apiService = {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async uploadPlaylist(data: FormData | { playlistUrl: string }): Promise<void> {
|
||||
try {
|
||||
const options: RequestInit = {
|
||||
method: 'POST',
|
||||
body: data instanceof FormData ? data : JSON.stringify(data),
|
||||
headers: data instanceof FormData ? {} : { 'Content-Type': 'application/json' },
|
||||
};
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/api/playlist`, options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error uploading playlist:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default apiService;
|
||||
|
||||
@@ -95,6 +95,13 @@ class SocketService {
|
||||
|
||||
this.socket.emit('update-channel', { id, updatedAttributes });
|
||||
}
|
||||
|
||||
// Playlist hochladen
|
||||
uploadPlaylist(data: FormData | { playlistUrl: string }) {
|
||||
if (!this.socket) throw new Error('Socket is not connected.');
|
||||
|
||||
this.socket.emit('upload-playlist', data);
|
||||
}
|
||||
}
|
||||
|
||||
const socketService = new SocketService();
|
||||
|
||||
Reference in New Issue
Block a user