Skip to content

Commit

Permalink
HLS/M3U Player
Browse files Browse the repository at this point in the history
  • Loading branch information
ozankaraali committed Dec 11, 2022
1 parent 3192ee2 commit 9346daa
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 54 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ You need to enter your IPTV provider's details to Settings. When you save, if yo
<img width="1392" alt="Screen Shot 2021-03-04 at 00 20 09" src="https://user-images.githubusercontent.com/19486728/109873914-7aed6080-7c7f-11eb-8bae-2af2588b2bac.png">
<img width="1392" alt="Screen Shot 2021-03-04 at 00 20 20" src="https://user-images.githubusercontent.com/19486728/109873933-7e80e780-7c7f-11eb-8bed-0000b17ec304.png">

## Future Work
## Disclaimer

This application bundles [a list of publicly available IPTV channels](https://github.com/iptv-org/iptv) from around the world. The channels are not hosted by this application or respective repository. The application simply creates a convenient way to browse a publicly available media database. The developer of this application has no affiliation with the content providers. The content provided can be removed at any time and we have no control over it. The developer assumes no liability and is not responsible for any legal issues caused by the misuse of this application.

No video files are stored in this repository, the application bundles open-sourced [iptv-org](https://github.com/iptv-org/iptv) playlist for quick startup, users can delete that playlist entry if they want to from their computer. If any links/channels in this application infringe on your rights as a copyright holder, they may be removed by sending a [pull request](https://github.com/iptv-org/iptv/pulls) or opening an [issue](https://github.com/iptv-org/iptv/issues/new?assignees=freearhey&labels=removal+request&template=--removal-request.yml&title=Remove%3A+). However, note that we have **no control** over the destination of the link, and just removing the link from the playlist will not remove its contents from the web. Note that linking does not directly infringe copyright because no copy is made on the site providing the link, and thus this is **not** a valid reason to send a DMCA notice to GitHub. To remove this content from the web, you should contact the web host that's actually hosting the content (**not** GitHub, nor the maintainers of this repository).

- UI work will be done
- Need to check Linux HWAccels

## Contributing

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "PiTV",
"version": "1.0.3-beta1",
"version": "1.1.0",
"description": "A cross-platform STB (IPTV) player client.",
"main": ".webpack/main",
"scripts": {
Expand Down Expand Up @@ -43,12 +43,12 @@
"bulma-prefers-dark": "^0.1.0-beta.1",
"classic-level": "^1.2.0",
"cors": "^2.8.5",
"electron-squirrel-startup": "^1.0.0",
"express": "^4.18.2",
"ffmpeg-static": "^5.1.0",
"fluent-ffmpeg": "^2.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"electron-squirrel-startup": "^1.0.0",
"sass": "^1.56.1",
"through2": "^4.0.2",
"video.js": "^7.20.3"
Expand Down
151 changes: 114 additions & 37 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,30 @@ import './App.scss';

const App = () => {
// stb or m3u => if true it is stb, else it is m3u
const [stb, setStb] = useState(false)
const [modalState, setModalState] = useState(false)
// take json state as account for the server url and mac address
const [currentStbAccount, setCurrentStbAccount] = useState({ url: "", mac: "" })
const [stbAccounts, setStbAccounts] = useState([])

let [config, setConfig] = useState(undefined)
const [selected, setSelected] = useState(0)
const [reload, setReload] = useState(false)
// get selected channel from channelList
const [selectedChannel, setSelectedChannel] = useState(undefined)

const [serverUrl, setServerUrl] = useState("")
const [macAddress, setMacAddress] = useState("")
const [fullScreen, setFullScreen] = useState(true)
const [reload, setReload] = useState(true)

const toggleModal = () => {
setModalState(!modalState)
}

useEffect(() => {
loadData();
}, [modalState])

useEffect(() => {
loadData();
}, [])

const loadData = async () => {
const response = await fetch("http://localhost:8000/config")
const data = await response.json()
setMacAddress(data.data[0].mac)
setServerUrl(data.data[0].url)
setConfig(data)
setSelected(data.selected)
}

const saveData = async () => {
Expand All @@ -41,29 +38,24 @@ const App = () => {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"selected": 1,
"data": [
{ "type": "STB", "url": serverUrl, "mac": macAddress },
{ "type": "STB", "url": "", "mac": "00:1A:79:xx:xx:xx", "channel_list": [] },
{ "type": "M3U8", "url": "" }
]
})
body: JSON.stringify(config),
})
const data = await response.json()
setReload(!reload)
setModalState(!modalState)
}

return (
<div className="App" id="app">
<div className="drag"></div>
<div className="columns is-gapless is-reversed-mobile">
<ChannelList reload={reload} />
<ChannelList reload={
() => {
setReload(!reload) // TODO: better reload
}
}/>
<div className="column">
<div>
<div className="buttons is-right">
<button className="button is-info first-button" onClick={() => setReload(!reload)}>Reload List</button>
<button className="button is-primary" onClick={() => toggleModal()}>Settings</button>
</div>
</div>
Expand All @@ -76,24 +68,109 @@ const App = () => {
<Modal
closeModal={() => toggleModal()}
saveModal={() => saveData()}

addServer={() => {
let configx = JSON.parse(JSON.stringify(config));
configx.data.push({ url: "", type: "STB" });
setSelected(configx.data.length - 1)
setConfig(configx);
}}

deleteServer={() => {
let configx = JSON.parse(JSON.stringify(config));
configx.selected = selected - 1;
configx.data.splice(selected, 1);
setSelected(selected - 1)
setConfig(configx);
}}

modalState={modalState}
title="PiTV Settings"
>
<div>
<p>Server URL (http://example.com:1234):</p>
<input className="input" type="text" placeholder="Server URL" value={serverUrl} onChange={e => {
setServerUrl(e.target.value)
}}></input>
</div>
Servers:
{
config && config.data.map((item, index) => {
return (
<div className="control">
<label className="radio">
<input type="radio" name="answer" checked={index === selected} key={index} onChange={() => {
setSelected(index)
let configx = JSON.parse(JSON.stringify(config));
configx.selected = index;
setConfig(configx);
}}></input>
{item.url ? item.url : "New Server"}
</label>
</div>
)
})
}

<p></p>
<div>
<p>MAC Address (00:1A:79:xx:xx:xx):</p>
<input className="input" type="text" placeholder="MAC Address" value={macAddress} onChange={e => {
setMacAddress(e.target.value)
}}></input>
</div>
</Modal>
</div>
Stream Type:
{
console.log(config && config.data[selected].type)}{
config && config.data[selected].type && ["STB", "M3UPLAYLIST", "M3USTREAM"].map((item, index) => {
return (
<div className="typeControl">
<label className="radio">
<input type="radio" name="typectrl" checked={item == config.data[selected].type} key={index} onChange={() => {
let configx = JSON.parse(JSON.stringify(config));
configx.data[selected].type = item;
setSelected(selected)
setConfig(configx);
}}></input>
{item}
</label>
</div>
)
})
}
<p></p>

{
config && config.data[selected] && (
<div>
<p>Server URL (http://example.com:1234):</p>
<input className="input" type="text" placeholder="Server URL" value={config.data[selected].url} onChange={e => {
let configx = JSON.parse(JSON.stringify(config));
configx.data[selected].url = e.target.value;
setConfig(configx);
}
}></input>
</div>
)
}

{
config && config.data[selected].type === "STB" && (
<div>
<p>MAC Address (00:1A:79:xx:xx:xx):</p>
<input className="input" type="text" placeholder="MAC Address" value={config.data[selected].mac} onChange={e => {
let configx = JSON.parse(JSON.stringify(config));
configx.data[selected].mac = e.target.value;
setConfig(configx);
}
}></input>
</div>
)
}

{/* { NOT YET SUPPORTED
config && config.data[selected].type === "M3UPLAYLIST" && (
<div>
<p>File:</p>
<input type="file" onChange={e => {
let configx = JSON.parse(JSON.stringify(config))
configx.data[selected].url = e.target.files[0].name;
setConfig(configx);
}}></input>
</div>
)
} */}

</Modal >
</div >
);
}
export default App;
16 changes: 12 additions & 4 deletions src/Modal.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

const Modal = ({ children, closeModal, saveModal, modalState, title }) => {
const Modal = ({ children, closeModal, saveModal, modalState, title, addServer, deleteServer}) => {
if (!modalState) {
return null;
}
Expand All @@ -16,13 +16,21 @@ const Modal = ({ children, closeModal, saveModal, modalState, title }) => {
<button className="delete" onClick={closeModal} />
</header>
<section className="modal-card-body">
<div className="content">
<div className="content" id="content">
{children}
</div>
</section>
<footer className="modal-card-foot">
<a className="button" onClick={closeModal}>Cancel</a>
<a className="button is-success" onClick={saveModal}>Save</a>
<div className="">
<a className="button is-primary is-light" onClick={addServer}>Add New</a>
<a className="button is-danger is-light" onClick={deleteServer}>Delete Selected</a>
</div>
{/* spacer div */}
<div className="is-flex-grow-1"></div>
<div className="">
<a className="button is-danger" onClick={closeModal}>Cancel</a>
<a className="button is-success" onClick={saveModal}>Save Settings</a>
</div>
</footer>
</div>
</div>
Expand Down
31 changes: 23 additions & 8 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ import ffmpegStatic from 'ffmpeg-static';
// "type": "M3UPLAYLIST",
// "url": "https://iptv-org.github.io/iptv/index.m3u"
// }
// {
// "type": "M3USTREAM",
// "url": "http://<URL>:8080/c/<CHANNEL_ID>/stream.m3u8"
// }
// ]
// }
// type: STB, M3UPLAYLIST, M3USTREAM
Expand Down Expand Up @@ -98,7 +102,16 @@ try {
console.log("options", options)
}
catch (e) {
console.log("error", e)
let config = {
"selected": 0,
"data": [
{
"type": "M3UPLAYLIST",
"url": "https://iptv-org.github.io/iptv/index.m3u"
}
]
}
db.put('config', config);
}

const do_handshake = async (url, mac) => {
Expand Down Expand Up @@ -294,27 +307,29 @@ app.get('/allChannels', async (req, res) => {
// Split the text into lines
const result = parseM3U(data);
// Return the result array
config.data[config.selected].data = result;
await db.put('config', config);
// config.data[config.selected].data = result;
// await db.put('config', config);
res.send(result);

}
else if (file) {
const data = fs.readFileSync(file, 'utf8');
const result = parseM3U(data);
config.data[config.selected].data = result;
await db.put('config', config);
// config.data[config.selected].data = result;
// await db.put('config', config);
res.send(result);
}
res.send([]);
else {
res.send([]);
}
} else if (type === 'M3USTREAM') {
const { url } = config.data[config.selected];
if (url) {
const channel = { id: 1, name: 'Stream ' + url, cmd: url };
const result = [channel];
// Return the result array
config.data[config.selected].data = result;
await db.put('config', config);
// config.data[config.selected].data = result;
// await db.put('config', config);
res.send(result);

}
Expand Down

0 comments on commit 9346daa

Please sign in to comment.