├── src
├── index.css
├── components
│ ├── Series
│ │ ├── Select.css
│ │ ├── Episode.js
│ │ ├── PlayerSeries.js
│ │ └── Seasons.js
│ ├── Search
│ │ ├── Search.css
│ │ └── Search.js
│ ├── Player
│ │ ├── Player.css
│ │ └── Buttons
│ │ │ ├── VolumeButton.css
│ │ │ ├── VolumeButton.js
│ │ │ ├── Button.js
│ │ │ └── PiPButton.js
│ ├── Epg-Fullscreen
│ │ ├── TimeBar
│ │ │ ├── TimeBar.js
│ │ │ ├── TimeLongLine.js
│ │ │ ├── TimeBarIndicator.js
│ │ │ └── TimeLine.js
│ │ ├── NavBar.js
│ │ ├── Day.js
│ │ ├── ProgrammeListingVIRTUAL.js.BK
│ │ ├── EpgChannel.js
│ │ ├── ProgrammeListing.js
│ │ ├── Programme-fake.js
│ │ ├── ProgrammeVIRTUAL.js.BK
│ │ └── Programme.js
│ ├── Vod
│ │ ├── NextArrow.js
│ │ ├── PlayerMag.js
│ │ ├── GroupRow.js
│ │ ├── PlayerVod.js
│ │ └── RowItem.js
│ ├── Live
│ │ ├── PulsingDot.js
│ │ ├── BLUE
│ │ │ ├── MainLiveBLUE.js_OLD
│ │ │ ├── ChannelEpgBLUE.js_OLD
│ │ │ ├── ChannelBLUE.js_OLD
│ │ │ └── ChannelsBLUE.js_OLD
│ │ ├── EpgListing.js
│ │ ├── ChannelEpgBar.js
│ │ ├── Tips.js
│ │ ├── Epg.js
│ │ ├── Channel.js
│ │ ├── Channels.js
│ │ ├── MainLive.js
│ │ └── ChannelEpg.js
│ ├── Group
│ │ ├── Groups.css
│ │ └── Groups.js
│ ├── LateralBar
│ │ └── LateralBar.css
│ ├── MainMenu
│ │ ├── MainMenu.js
│ │ └── MainMenu.css
│ ├── NavBar.js
│ ├── Group-old
│ │ └── Groups.js
│ ├── Popup
│ │ └── Popup.js
│ ├── AccountInfo.js
│ └── Login.js
├── actions
│ ├── timer5.js
│ ├── timer60.js
│ ├── set-Group.js
│ ├── h24.js
│ ├── epgPopup.js
│ ├── set-Playlist.js
│ ├── volume.js
│ ├── fullScreen.js
│ ├── playingChannel.js
│ └── set-Playlist-Episodes.js
├── other
│ ├── focused-element.js
│ ├── group-by.js
│ ├── delay.js
│ ├── getEpg.js
│ ├── convert-day-name.js
│ ├── last-opened-mode.js
│ ├── convert-rem.js
│ ├── Controls
│ │ ├── Navbar.js
│ │ ├── Group.js
│ │ └── Channel.js
│ ├── Player-github
│ │ └── style.css
│ ├── user_info.js
│ ├── InfiniteGrid
│ │ └── item.js
│ ├── generate-url.js
│ ├── local-db.js
│ ├── key-navigation.js
│ ├── vod-series-name-optimizer.js
│ ├── axios.js
│ ├── Transition.css
│ └── auth.js
├── reducer
│ ├── groups.js
│ ├── playlist.js
│ ├── epgPopup.js
│ ├── fullScreen.js
│ ├── playingChannel.js
│ ├── playlist-episodes.js
│ ├── timer5s.js
│ ├── timer60s.js
│ ├── h24.js
│ ├── volume.js
│ └── index.js
├── index.js
├── App.css
├── App.js
└── serviceWorker.js
├── PORT.env
├── .env
├── .env.development
├── .env.production
├── public
├── robots.txt
├── favicon.ico
├── img
│ ├── dot.gif
│ ├── banner_b.png
│ ├── banner_w.png
│ ├── no_cover.jpg
│ └── placeholder-channel-events_border.svg
├── config.css
├── manifest.json
├── sql_table.sql
├── config.php
├── epg.php
├── README.txt
├── config.js
├── index.html
└── proxy.php
├── github-pic
├── top.png
├── vod.png
└── channels.png
├── .gitignore
├── package.json
└── README.md
/src/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/PORT.env:
--------------------------------------------------------------------------------
1 | PORT=3005
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
2 |
--------------------------------------------------------------------------------
/src/components/Series/Select.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | REACT_APP_IPTVEDITOR_API=https://api.iptveditor.com/
2 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | REACT_APP_IPTVEDITOR_API=https://api.iptveditor.com/
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/github-pic/top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/github-pic/top.png
--------------------------------------------------------------------------------
/github-pic/vod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/github-pic/vod.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/dot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/public/img/dot.gif
--------------------------------------------------------------------------------
/github-pic/channels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/github-pic/channels.png
--------------------------------------------------------------------------------
/public/img/banner_b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/public/img/banner_b.png
--------------------------------------------------------------------------------
/public/img/banner_w.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/public/img/banner_w.png
--------------------------------------------------------------------------------
/public/img/no_cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/HEAD/public/img/no_cover.jpg
--------------------------------------------------------------------------------
/src/actions/timer5.js:
--------------------------------------------------------------------------------
1 | export const setTimer5 = () => {
2 | return{
3 | type: "SET_TIMER_5",
4 | payload: Date.now()
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/actions/timer60.js:
--------------------------------------------------------------------------------
1 | export const setTimer60 = () => {
2 | return{
3 | type: "SET_TIMER_60",
4 | payload: Date.now()
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/actions/set-Group.js:
--------------------------------------------------------------------------------
1 | export const setGroupList = (groups) => {
2 | return{
3 | type: "SET_GROUP",
4 | payload: groups
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/actions/h24.js:
--------------------------------------------------------------------------------
1 | export const setH24 = (h24) => {
2 | return{
3 | type: "SET_H24",
4 | payload: h24 ? "HH:MM" : "hh:MM TT"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/other/focused-element.js:
--------------------------------------------------------------------------------
1 | let focused;
2 | exports.getFocus = () =>{
3 | return focused;
4 | }
5 |
6 | exports.setFocus = (el) =>{
7 | focused = el;
8 | }
--------------------------------------------------------------------------------
/src/actions/epgPopup.js:
--------------------------------------------------------------------------------
1 | export const setEpgPopup = (payload = null) => {
2 | return{
3 | type: "SET_EPG_POPUP",
4 | payload: payload
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/actions/set-Playlist.js:
--------------------------------------------------------------------------------
1 | export const setPlaylist = (playlist) => {
2 | return{
3 | type: "SET_PLAYLIST",
4 | payload: playlist
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/actions/volume.js:
--------------------------------------------------------------------------------
1 | export const setVolume = (add = false) => {
2 | return{
3 | type: "SET_VOLUME",
4 | payload: add ? 10 : -10
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/actions/fullScreen.js:
--------------------------------------------------------------------------------
1 | export const setFullScreen = (fullscreen) => {
2 | return{
3 | type: "SET_FULLSCREEN",
4 | payload: fullscreen
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/actions/playingChannel.js:
--------------------------------------------------------------------------------
1 | export const setPlayingChannel = (channel) => {
2 | return{
3 | type: "SET_CHANNEL",
4 | payload: channel
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/config.css:
--------------------------------------------------------------------------------
1 | :root{
2 | --second-color: #ec1650; /*use colors easily readable on white background*/
3 | --second-color-shadow: #ec165008;
4 | --first-color: #171c22;
5 | }
6 |
--------------------------------------------------------------------------------
/src/actions/set-Playlist-Episodes.js:
--------------------------------------------------------------------------------
1 | export const setPlaylistEpisodes = (playlist) => {
2 | return{
3 | type: "SET_PLAYLIST_EPISODES",
4 | payload: playlist
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/other/group-by.js:
--------------------------------------------------------------------------------
1 | export const groupBy = (list, id) => {
2 | return list.reduce(function(results, item) {
3 | (results[item[id]] = results[item[id]] || []).push(item);
4 | return results;
5 | }, {})
6 | }
--------------------------------------------------------------------------------
/src/other/delay.js:
--------------------------------------------------------------------------------
1 | function sleep(delayInms) {
2 | return new Promise(resolve => {
3 | setTimeout(() => {
4 | resolve(2);
5 | }, delayInms);
6 | });
7 | }
8 |
9 | exports.delay = sleep;
10 | exports.sleep = sleep;
--------------------------------------------------------------------------------
/src/other/getEpg.js:
--------------------------------------------------------------------------------
1 | exports.getEpg = (epg,id) => {
2 | let toReturn = id && epg && epg.find(x=>id === x.id && x.start <= Date.now() && x.end > Date.now());
3 | if(!toReturn)
4 | return {start:undefined}
5 | else return toReturn;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/src/reducer/groups.js:
--------------------------------------------------------------------------------
1 | const groupsReducer = (state = [], action) => {
2 | switch(action.type){
3 | case "SET_GROUP":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
10 | export default groupsReducer;
--------------------------------------------------------------------------------
/src/reducer/playlist.js:
--------------------------------------------------------------------------------
1 | const playlistReducer = (state = [], action) => {
2 | switch(action.type){
3 | case "SET_PLAYLIST":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
10 | export default playlistReducer;
--------------------------------------------------------------------------------
/src/reducer/epgPopup.js:
--------------------------------------------------------------------------------
1 | const epgPopupReducer = (state = null, action) => {
2 | switch(action.type){
3 | case "SET_EPG_POPUP":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
10 | export default epgPopupReducer;
--------------------------------------------------------------------------------
/src/reducer/fullScreen.js:
--------------------------------------------------------------------------------
1 |
2 | const fullScreenReducer = (state = "", action) => {
3 | switch(action.type){
4 | case "SET_FULLSCREEN":
5 | return action.payload;
6 | default:
7 | return state;
8 | }
9 | }
10 |
11 | export default fullScreenReducer;
--------------------------------------------------------------------------------
/src/reducer/playingChannel.js:
--------------------------------------------------------------------------------
1 |
2 | const playingChannelReducer = (state = null, action) => {
3 | switch(action.type){
4 | case "SET_CHANNEL":
5 | return {...action.payload};
6 | default:
7 | return state;
8 | }
9 | }
10 |
11 | export default playingChannelReducer;
--------------------------------------------------------------------------------
/src/reducer/playlist-episodes.js:
--------------------------------------------------------------------------------
1 | const playlistEpisodeReducer = (state = [], action) => {
2 | switch(action.type){
3 | case "SET_PLAYLIST_EPISODES":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
10 | export default playlistEpisodeReducer;
--------------------------------------------------------------------------------
/src/components/Search/Search.css:
--------------------------------------------------------------------------------
1 | .blur-search {
2 | backdrop-filter: blur(.3rem);
3 | background-color: rgba(0, 0, 0, .9);
4 | opacity: 1 !important;
5 | }
6 |
7 | @supports ((-webkit-backdrop-filter: blur(.3em)) or (backdrop-filter: blur(.3em))) {
8 | .blur-search {
9 | background-color: rgba(0, 0, 0, 0) !important;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/reducer/timer5s.js:
--------------------------------------------------------------------------------
1 | const timer5Reducer = (state = Date.now(), action) => {
2 | switch(action.type){
3 | case "SET_TIMER_5":
4 | if(action.payload - state > 1000)
5 | return action.payload;
6 | return state;
7 | default:
8 | return state;
9 | }
10 | }
11 |
12 | export default timer5Reducer;
--------------------------------------------------------------------------------
/src/reducer/timer60s.js:
--------------------------------------------------------------------------------
1 | const timer60Reducer = (state = Date.now(), action) => {
2 | switch(action.type){
3 | case "SET_TIMER_60":
4 | if(action.payload - state > 5000)
5 | return action.payload;
6 | return state;
7 | default:
8 | return state;
9 | }
10 | }
11 |
12 | export default timer60Reducer;
--------------------------------------------------------------------------------
/src/other/convert-day-name.js:
--------------------------------------------------------------------------------
1 | const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
2 |
3 | exports.dateToName = (date) =>{
4 | let d = new Date(date);
5 | return days[d.getDay()];
6 | }
7 |
8 | exports.dateToReadable = (date) =>{
9 | let d = new Date(date);
10 | return `${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}`
11 | }
--------------------------------------------------------------------------------
/src/reducer/h24.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const h24Reducer = (state = Cookies.get("h24")||"HH:MM", action) => {
4 | switch(action.type){
5 | case "SET_H24":
6 | Cookies.set("h24",action.payload,{ expires: 365 })
7 | return action.payload;
8 | default:
9 | return state;
10 | }
11 | }
12 |
13 | export default h24Reducer;
--------------------------------------------------------------------------------
/src/other/last-opened-mode.js:
--------------------------------------------------------------------------------
1 | let oldMode = "";
2 | let previousGroup = -1;
3 |
4 | exports.resetMemory = (mode) =>{
5 | if(mode !== oldMode){
6 | oldMode = mode;
7 | previousGroup = -1;
8 | return true;
9 | }return false;
10 | }
11 |
12 | exports.setGroup = (group) =>{
13 | previousGroup = group;
14 | }
15 |
16 | exports.getGroup = () => {
17 | return previousGroup;
18 | }
--------------------------------------------------------------------------------
/src/other/convert-rem.js:
--------------------------------------------------------------------------------
1 | exports.convertRemToPixels = (rem) => {
2 | return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
3 | }
4 |
5 | exports.convertVhToPixels = (value) => {
6 | var parts = value.match(/([0-9\.]+)(vh|vw)/)
7 | var q = Number(parts[1])
8 | var side = window[['innerHeight', 'innerWidth'][['vh', 'vw'].indexOf(parts[2])]]
9 | return side * (q/100)
10 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /OLD
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/src/components/Player/Player.css:
--------------------------------------------------------------------------------
1 | .video-overlay {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | background-color: rgba(0, 0, 0, .7);
8 | z-index: 2;
9 | }
10 |
11 | video,
12 | .ie8-poster {
13 | z-index: 1;
14 | background-color: rgba(0, 0, 0, .7);
15 |
16 | }
17 |
18 | .text-copy {
19 | position: absolute;
20 | top: 5%;
21 | left: 5%;
22 | z-index: 2;
23 | text-shadow: 1px 1px 1px #000;
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/components/Player/Buttons/VolumeButton.css:
--------------------------------------------------------------------------------
1 | .volume-container:hover > input{
2 | width: 6em;
3 | visibility: visible;
4 | transition: width .6s, visibility 0s ease;
5 |
6 | }
7 |
8 | .volume-container > input:focus{
9 | outline: 0!important;
10 | }
11 |
12 | .volume-container > input{
13 | width:0;
14 | visibility: hidden;
15 | transition: all .6s ease;
16 | margin-left: -0.5em;
17 | }
18 |
19 | .volume-container:hover{
20 | width: 10em;
21 | }
--------------------------------------------------------------------------------
/src/other/Controls/Navbar.js:
--------------------------------------------------------------------------------
1 | import {nextInput} from "../key-navigation"
2 |
3 | export const NavbarKeyboard = {
4 | down: (focus) =>{
5 | return nextInput(focus,document.querySelectorAll(".lateral-focus"),"lateral-focus", false) || focus;
6 | },
7 | up: (focus) =>{
8 | return nextInput(focus,document.querySelectorAll(".lateral-focus"),"lateral-focus", true) || focus;
9 | },
10 | searchFocus: (focus) =>{
11 | return document.querySelector(".lateral-focus")
12 | },
13 | right: () => 1
14 | }
15 |
--------------------------------------------------------------------------------
/src/reducer/volume.js:
--------------------------------------------------------------------------------
1 |
2 | const volumeReducer = (state = 50, action) => {
3 | switch(action.type){
4 | case "SET_VOLUME":
5 | let newVolume = state + action.payload;
6 | if((state >= 100 && newVolume >=100)||(state <= 0 && newVolume <=0))
7 | return state
8 | else if(newVolume > 100)
9 | newVolume = 100;
10 | else if(newVolume < 0)
11 | newVolume = 0;
12 | return newVolume;
13 | default:
14 | return state;
15 | }
16 | }
17 |
18 | export default volumeReducer;
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/other/Controls/Group.js:
--------------------------------------------------------------------------------
1 | export const GroupKeyboard = {
2 | down: (focus) =>{
3 | if(!focus.nextElementSibling){
4 | let list = Array.from(document.querySelectorAll(".group"));
5 | return list[list.length-1]
6 | }else return focus.nextElementSibling
7 | },
8 | up: (focus) =>{
9 | return focus.previousElementSibling || document.querySelector(".group") || focus;
10 | },
11 | searchFocus: (focus) =>{
12 | return document.getElementById("selectedGp") || document.querySelector(".group")
13 | },
14 | left: () => 1,
15 | enabled: true
16 | }
17 |
--------------------------------------------------------------------------------
/public/sql_table.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS `epg_data`;
2 |
3 | CREATE TABLE `epg_data` (
4 | `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
5 | `start_timestamp` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
6 | `stop_timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
7 | `title` varchar(255) DEFAULT NULL,
8 | `description` text DEFAULT NULL
9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
10 |
11 |
12 | ALTER TABLE `epg_data`
13 | ADD PRIMARY KEY (`id`,`start_timestamp`),
14 | ADD UNIQUE KEY `id` (`id`,`start_timestamp`),
15 | ADD UNIQUE KEY `id_2` (`id`,`stop_timestamp`);
16 | COMMIT;
17 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/TimeBar/TimeBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import TimeBarIndicator from "./TimeBarIndicator"
4 | import TimeLine from "./TimeLine"
5 |
6 | const Container = styled.div`
7 | position: sticky;
8 | display: block;
9 | top: 0px;
10 | margin: 0px;
11 | width: 408rem;
12 | background: #fff;
13 | scroll-behavior: smooth;
14 | box-shadow: 0 3px 8px 0 rgb(0 0 0 / 12%);
15 | `
16 |
17 |
18 |
19 | const TimeBar = ({day, h24, scrollBtn}) => {
20 | return (
21 |
22 |
23 | {day===0 && ()}
24 |
25 | )
26 | }
27 |
28 | export default TimeBar
29 |
--------------------------------------------------------------------------------
/public/config.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Player/Buttons/VolumeButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Button from "./Button"
3 | import "./VolumeButton.css"
4 |
5 | const VolumeButton = ({enabled, volume, onChangeInput, onClick}) => {
6 | return (
7 |
8 |
19 | )
20 | }
21 |
22 | export default VolumeButton
23 |
--------------------------------------------------------------------------------
/src/other/Player-github/style.css:
--------------------------------------------------------------------------------
1 | .progress-netflix {
2 | -webkit-appearance: none;
3 | width: 100%;
4 | height: .3rem;
5 | outline: none;
6 | opacity: 0.7;
7 | cursor:pointer;
8 | transition: height 0.2s ease, opacity .2s ease;
9 | }
10 |
11 | .progress-netflix:hover {
12 | opacity: 1;
13 | height: .7rem;
14 | }
15 |
16 | .progress-netflix::-webkit-slider-thumb {
17 | -webkit-appearance: none;
18 | appearance: none;
19 | width: 1.4rem;
20 | height: 1.4rem;
21 | border-radius: 50%;
22 | background: var(--second-color)!important;
23 | cursor: pointer;
24 | }
25 |
26 | .progress-netflix::-moz-range-thumb {
27 | width: 1.4rem;
28 | height: 1.4rem;
29 | border-radius: 50%;
30 | background: var(--second-color)!important;
31 | cursor: pointer;
32 | }
--------------------------------------------------------------------------------
/src/other/Controls/Channel.js:
--------------------------------------------------------------------------------
1 | let focusMainChannelKeyboard = null;
2 |
3 |
4 | export const MainChannelKeyboard = {
5 | down: (focus) =>{
6 | if(focus && focus.nextElementSibling)
7 | focusMainChannelKeyboard = focus.nextElementSibling;
8 | return focusMainChannelKeyboard;
9 | },
10 | up: (focus) =>{
11 | if(focus && focus.previousElementSibling)
12 | focusMainChannelKeyboard = focus.previousElementSibling;
13 | return focusMainChannelKeyboard;
14 | },
15 | searchFocus: (focus) =>{
16 | if(focusMainChannelKeyboard && document.body.contains(focusMainChannelKeyboard))
17 | return focusMainChannelKeyboard;
18 | return document.getElementById("selectedCh") || document.querySelector(".channel")
19 | },
20 | backMenu: true,
21 | enabled:false
22 | }
23 |
--------------------------------------------------------------------------------
/public/epg.php:
--------------------------------------------------------------------------------
1 | []);
20 |
21 |
22 |
23 | if(!$_POST["epg_id"] || !$_POST["start"] || !$_POST["stop"]){
24 | echo json_encode($list);
25 | return;
26 | }
27 |
28 | $list->epg_listings = getEpg($_POST["epg_id"], $_POST["start"], $_POST["stop"]);
29 |
30 |
31 | echo json_encode($list);
--------------------------------------------------------------------------------
/src/reducer/index.js:
--------------------------------------------------------------------------------
1 | import playingChannelReducer from "./playingChannel";
2 | import playlistEpisodeReducer from "./playlist-episodes";
3 |
4 | import groupsReducer from "./groups";
5 | import playlistReducer from "./playlist";
6 | import timer60Reducer from "./timer60s";
7 | import timer5Reducer from "./timer5s";
8 | import epgPopupReducer from "./epgPopup";
9 | import h24Reducer from "./h24";
10 | import {combineReducers} from "redux";
11 |
12 | const allReducer = combineReducers({
13 | playingCh : playingChannelReducer, // solo counterReducer => counterReducer : counterReducer
14 | timer60 : timer60Reducer,
15 | timer5 : timer5Reducer,
16 | h24 : h24Reducer,
17 | epgPopup : epgPopupReducer,
18 | groupsList : groupsReducer,
19 | playlist : playlistReducer,
20 | playlistEpisodes : playlistEpisodeReducer,
21 | })
22 |
23 | export default allReducer;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | import {createStore} from "redux";
7 | import allReducer from "./reducer/index"
8 | import {Provider} from "react-redux"
9 |
10 | const myStore = createStore(
11 | allReducer,
12 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
13 | );
14 |
15 |
16 | ReactDOM.render(
17 |
18 |
19 |
20 |
21 | ,
22 | document.getElementById('root')
23 | );
24 |
25 | // If you want your app to work offline and load faster, you can change
26 | // unregister() to register() below. Note this comes with some pitfalls.
27 | // Learn more about service workers: https://bit.ly/CRA-PWA
28 | serviceWorker.unregister();
29 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/TimeBar/TimeLongLine.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import {useSelector} from "react-redux"
4 | import { useState, useEffect, useMemo } from 'react'
5 |
6 | const Line = styled.div`
7 | width: 0.1rem;
8 | position: absolute;
9 | top: 0;
10 | height: 100%;
11 | background: #064497;
12 | `
13 |
14 | const TimeLongLine = () => {
15 | const timer = useSelector(state => state.timer60);
16 | const date = useMemo(() => new Date(timer),[timer]);
17 |
18 | const [length, setLength] = useState((date.getHours()*17) + (date.getMinutes()*0.25));
19 | useEffect(() => {
20 | setLength((date.getHours()*17) + (date.getMinutes()*0.25));
21 | },[timer,date])
22 | return (
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default TimeLongLine
30 |
--------------------------------------------------------------------------------
/public/README.txt:
--------------------------------------------------------------------------------
1 | 1. Open with a text editor (for example notepad++) config.js and complete empty fields ( window.dns). More instructions are available inside the file.
2 | 2. Open with a text editor (for example notepad++) config.php and write your mysql database info (database url, database name, username, password) and epg xml url. (if you don't use epg you can skip part 2 and 3)
3 | 3. Import "sql_table.sql" in your mysql database (if you are using phpMyAdmin click on "import" -> select the sql_table.sql and click on execute)
4 |
5 | 4. [OPTIONAL] Open with a text editor (for example notepad++) config.css if you want to change main color and background one.
6 | 5. [OPTIONAL] Change favicon.ico and img > banner_w.png
7 |
8 | 6. Copy and paste all the files in your server. Use the root folder, like http://domain.com/ (don't use folders like http://domain.com/player/ !)
9 |
10 | To avoid wasting server resources, epg update will be triggered when an user login and database has less than 12 hours of programmes.
11 |
--------------------------------------------------------------------------------
/src/other/user_info.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 | import * as Axios from "./axios"
3 |
4 | const userInfo = {};
5 |
6 | export function getInfo(){
7 | return userInfo;
8 | }
9 |
10 | export function setInfo(data, serverInfo){
11 | userInfo.expiry = new Date(parseInt(data.exp_date+"000"));
12 | userInfo.username = data.username;
13 | userInfo.password = data.password;
14 | userInfo.max_connections = data.max_connections;
15 | userInfo.message = data.message;
16 |
17 | Axios.post("/epg.php",{init:1, username: userInfo.username, password: userInfo.password,
18 | dns: `${serverInfo.server_protocol}://${serverInfo.url}:${serverInfo.port}`
19 | },true);
20 |
21 | Cookies.set("username",data.username,{ expires: 365 });
22 | Cookies.set("password",data.password,{ expires: 365 });
23 | }
24 |
25 | export function logout(){
26 | Cookies.remove("dns");
27 | Cookies.remove("username");
28 | Cookies.remove("password");
29 | window.location = "/";
30 | }
--------------------------------------------------------------------------------
/src/components/Vod/NextArrow.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from 'styled-components'
3 | import {Link} from "react-router-dom"
4 |
5 | const Li = styled(Link)`
6 | height: calc(22.5vw);
7 | max-height: 60vh;
8 |
9 | width: 15.266667%;
10 | flex: 0 0 15.266667%;
11 |
12 | position: absolute;
13 | right: calc(-7% - 0.4rem);
14 |
15 | padding-left: calc(5% - 0.4rem);
16 |
17 | opacity: 0;
18 | transition: opacity 0.2s ease;
19 | color:white;
20 |
21 | cursor: pointer;
22 |
23 | &:hover, &:focus, & > i:focus{
24 | opacity: 1;
25 | color:white;
26 | outline-width: 0;
27 |
28 | }
29 |
30 |
31 |
32 | `
33 |
34 | const I = styled.i`
35 | font-size: 3vw;
36 | padding-top: calc(6.5vw * 1.5);
37 | `
38 |
39 |
40 | const NextArrow = ({category_id, isSeries}) => {
41 | return (
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default NextArrow
49 |
--------------------------------------------------------------------------------
/public/config.js:
--------------------------------------------------------------------------------
1 | /*----- Player name -----*/
2 | window.playername = "IPTVEditor Web Player";
3 |
4 | /*----- DNS -----*/
5 | //Iptv provider dns url (for example "http://domain.com:80")
6 | window.dns = "http://domain.com:80";
7 |
8 | /*----- CORS -----*/
9 | /*/ False if iptv provider has the Access-Control-Allow-Origin set on "*" or allows your player domain.
10 | It will work with most of the providers.
11 |
12 | Change in "true" in the other case. Player will use "proxy.php" file to manage the requests.
13 | If true, change also the $cors value in proxy.php
14 | */
15 | window.cors = false; //true
16 |
17 | /*---- HTTPS -----*/
18 | /* If streams are using an ssl protol change this box on true.*/
19 | window.https = false; //true;
20 |
21 | /*----- TMDB API [OPTIONAL] -----*/
22 | /* By default player will use movie info from the provider. In case these info are missing it will be used tmdb as alternative
23 | Here you can get a tmdb api key: https://developers.themoviedb.org/3/getting-started/introduction */
24 | window.tmdb = "";
25 |
26 |
27 |
28 |
29 |
30 | //!!!!!!! Don't change this !!!!!!!
31 | document.getElementsByTagName("title")[0].innerText = window.playername
--------------------------------------------------------------------------------
/src/components/Live/PulsingDot.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from "styled-components"
3 |
4 | /*
5 | const Pulse = styled.div`
6 | border: .3rem solid #62bd19;
7 | -webkit-border-radius: 99rem;
8 | height: 2rem;
9 | width: 2rem;
10 | position: absolute;
11 | left: -0.55rem;
12 | top: -.5rem;
13 | -webkit-animation: pulsate 1s ease-out;
14 | -webkit-animation-iteration-count: infinite;
15 | opacity: 0.0;
16 |
17 | @keyframes pulsate {
18 | 0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.0;}
19 | 50% {opacity: 1.0;}
20 | 100% {-webkit-transform: scale(1.2, 1.2); opacity: 0.0;}
21 | }
22 | `*/
23 |
24 | const Circle = styled.div`
25 | content: "";
26 | top: calc(50%);
27 | display: inline-block;
28 | height: .7rem;
29 | width: .7rem;
30 | border-radius: 50%;
31 | background-color: #b9b9b9;
32 | `
33 |
34 |
35 | const PulsingDot = ({isPlaying}) => {
36 |
37 | const CircleStyle = {backgroundColor: isPlaying ? "#54ca28" : "b9b9b9"};
38 |
39 | return (
40 |
41 | {isPlaying ?

:
}
42 |
43 | )
44 | }
45 |
46 | export default PulsingDot
47 |
--------------------------------------------------------------------------------
/src/components/Live/BLUE/MainLiveBLUE.js_OLD:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Player from "../Player"
3 | import EpgSummary from "../EpgListing"
4 | import Channels from "../Channels"
5 | import styled from "styled-components"
6 | import {useSelector} from "react-redux"
7 |
8 |
9 | const Main = styled.div`
10 | padding: 3vh 1vw;
11 | background-color: /*#171c22;*/ #203d79;
12 | height: calc(100vh - 3rem);
13 | `
14 |
15 |
16 | const MainLive = () => {
17 | const playingChannel = useSelector(state => state.playingCh);
18 | const fullScreen = useSelector(state => state.fullScreen);
19 |
20 | return (
21 |
22 |
23 |
24 |
{playingChannel.name}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
36 | export default MainLive
37 |
--------------------------------------------------------------------------------
/src/other/InfiniteGrid/item.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {isEqual} from 'lodash';
3 |
4 | export default class Item extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | _itemWidth() {
11 | return this.props.dimensions.gridWidth / this.props.dimensions.itemsPerRow;
12 | }
13 |
14 | _itemLeft() {
15 | var column = this.props.index % this.props.dimensions.itemsPerRow;
16 | return column * (this.props.dimensions.gridWidth / this.props.dimensions.itemsPerRow);
17 | }
18 |
19 | _itemTop() {
20 | return Math.floor(this.props.index / this.props.dimensions.itemsPerRow) * this.props.dimensions.height;
21 | }
22 |
23 | // LIFECYCLE
24 |
25 | shouldComponentUpdate(nextProps, nextState) {
26 | return !isEqual(this.props, nextProps);
27 | }
28 |
29 | // RENDER
30 |
31 | render() {
32 | const _style = {
33 | width: this._itemWidth() - this.props.padding,
34 | height: this.props.dimensions.height - this.props.padding,
35 | left: this._itemLeft(),
36 | top: this._itemTop(),
37 | position: 'absolute'
38 | };
39 |
40 | var props = {
41 | className: 'item',
42 | style: _style
43 | };
44 |
45 | return (
46 |
47 |
{this.props.data}
48 |
49 | );
50 |
51 | }
52 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iptveditor_player",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@reduxjs/toolkit": "^1.5.0",
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.5.0",
9 | "@testing-library/user-event": "^7.2.1",
10 | "axios": "^0.21.1",
11 | "dateformat": "^4.5.1",
12 | "js-cookie": "^2.2.1",
13 | "node-pre-gyp": "^0.17.0",
14 | "qs": "^6.5.2",
15 | "react": "^17.0.1",
16 | "react-dom": "^17.0.1",
17 | "react-icons": "^4.2.0",
18 | "react-player": "^2.9.0",
19 | "react-redux": "^7.2.2",
20 | "react-router-dom": "^5.2.0",
21 | "react-scripts": "^4.0.3",
22 | "react-window": "^1.8.6",
23 | "styled-components": "^5.2.1"
24 | },
25 | "scripts": {
26 | "start": "set PORT=3006 && react-scripts start",
27 | "build": "set GENERATE_SOURCEMAP=false && react-scripts build",
28 | "test": "react-scripts test",
29 | "eject": "react-scripts eject"
30 | },
31 | "eslintConfig": {
32 | "extends": "react-app"
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version",
44 | "iOS 7"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Player/Buttons/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const ButtonObj = styled.button`
5 | cursor: pointer;
6 | position: relative;
7 | text-align: center;
8 | margin: 0;
9 | padding: 0;
10 | height: 100%;
11 | width: 4em;
12 | -webkit-flex: none;
13 | flex: none;
14 | background: none;
15 | border: none;
16 | color: inherit;
17 | display: inline-block;
18 | font-size: inherit;
19 | line-height: inherit;
20 | text-transform: none;
21 | text-decoration: none;
22 | transition: none;
23 | -webkit-appearance: none;
24 | appearance: none;
25 | transition: text-shadow .2s ease;
26 |
27 | &:focus{
28 | outline: 0!important;
29 | }
30 |
31 | &:hover{
32 | text-shadow: 0em 0em 1em white;
33 | }
34 |
35 | `
36 |
37 | const PopupText = styled.span`
38 | border: 0;
39 | clip: rect(0 0 0 0);
40 | height: 1px;
41 | overflow: hidden;
42 | padding: 0;
43 | position: absolute;
44 | width: 1px;
45 | `
46 |
47 | const Button = ({onClick, enabled, iconOn, iconOff, textOn, textOff}) => {
48 | return (
49 |
50 |
51 | {!enabled ? textOn : textOff}
52 |
53 | )
54 | }
55 |
56 | export default Button
57 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/NavBar.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import Day from "./Day"
4 | import {dateToName, dateToReadable} from "../../other/convert-day-name"
5 |
6 | const Nav = styled.div`
7 | background: #064497;
8 | border: none;
9 | box-shadow: inset 0 5px 10px 0 rgb(0 0 0 / 10%);
10 | `
11 |
12 | const DaysList = styled.ul`
13 | list-style: none;
14 | padding-left: 0;
15 | margin: 0;
16 | display: flex;
17 | align-items: center;
18 | overflow-x: auto;
19 | display: block;
20 | justify-content: space-between;
21 | flex-flow: row wrap;
22 | align-items: center;
23 | padding: 1rem 2rem;
24 | width: 100%;
25 |
26 | `
27 |
28 | const NavBar = ({day}) => {
29 | return (
30 |
39 | )
40 | }
41 |
42 |
43 | export default NavBar
44 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/Day.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import {Link, useLocation } from "react-router-dom";
4 |
5 | const DayLi = styled.li`
6 | margin-right: .9375rem;
7 | display: inline;
8 |
9 | & > a{
10 | padding: 0;
11 | color: #fff;
12 | background-color: transparent;
13 | border-radius: 1.375rem;
14 | text-decoration: none;
15 | white-space: nowrap;
16 | font-size:.9rem
17 |
18 | }
19 |
20 | & > a > span{
21 | }`
22 |
23 | const selectedDay = {
24 | alignItems: "center",
25 | lineHeight: "1.2",
26 | padding: ".35rem 1.5625rem",
27 | maxHeight: "2.8rem",
28 | borderRadius: "1.375rem",
29 | backgroundColor: "#2359a2",
30 | overflow: "hidden",
31 | textTransform: "uppercase"
32 | }
33 |
34 |
35 |
36 | function Day({name, isSelected, date}) {
37 | const location = useLocation();
38 |
39 | return (
40 |
41 |
42 | {name}
43 |
44 |
45 | )
46 | }
47 |
48 | const generateUrlFromDate = (url, date) =>{
49 | if(!date)
50 | date = "";
51 | url = url.replace(/\/$/,"");
52 | let splitted = url.split("/");
53 | let d = splitted[splitted.length-1];
54 | if(isNaN(new Date(d).getTime())){
55 | return url + "/" + date + "/";
56 | }else return `${splitted.slice(0,-1).join("/")}/${date ? date+"/" : ""}`;
57 |
58 | }
59 |
60 | export default Day
61 |
--------------------------------------------------------------------------------
/src/components/Live/EpgListing.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import styled from "styled-components"
3 | import EpgRow from "./Epg"
4 | import {useSelector} from "react-redux"
5 | import {getEpgNow} from "../../other/epg-database"
6 |
7 |
8 | const EpgSummary = styled.div`
9 | margin-top: 1rem;
10 | max-height: 40vh;
11 | overflow: hidden;
12 | `
13 |
14 | const EpgListing = ({Epg, Shift}) => {
15 | const timer = useSelector(state => state.timer60);
16 |
17 | const [epgListing, setEpgListing] = useState([]);
18 |
19 | useEffect(()=>{
20 | const arr = getEpgNow(Epg,Shift);
21 | if(!arr || arr.length === 0 )
22 | setEpgListing([]);
23 | else setEpgListing(arr);
24 | },[Epg,Shift]);
25 |
26 |
27 | useEffect(() => {
28 | const arr = getEpgNow(Epg,Shift);
29 | if(!arr || arr.length === 0 )
30 | setEpgListing([]);
31 | else if(arr && arr.length > 0 && epgListing.length>0){
32 | if(arr[0].start !== epgListing[0].start)
33 | setEpgListing(arr);
34 | }else if(arr && arr.length >0 && (!epgListing || epgListing.length===0)){
35 | setEpgListing(arr);
36 | }
37 | }, [timer, Epg, Shift]);
38 |
39 | return (
40 |
41 | {epgListing.slice(0, 4).map((epg,id) => (
42 |
43 | ))}
44 |
45 | )
46 | }
47 |
48 | export default EpgListing
49 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/TimeBar/TimeBarIndicator.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import {useSelector} from "react-redux"
3 | import React from "react"
4 |
5 | const dateFormat = require("dateformat");
6 |
7 | const Container = styled.div`
8 | z-index: 10000000000000;
9 | top: 1.3875rem;
10 | position: absolute;
11 | left: 0;
12 | height: 35px;
13 |
14 | & > div{
15 | top: 1.1.3875rem;
16 | position: absolute;
17 | left: 0;
18 | height: 35px;
19 | }
20 | `
21 | const Button = styled.button`
22 | width: 81px;
23 | padding: .25rem 1.5rem;
24 | text-align: center;
25 | color: #fff;
26 | background-color: #064497;
27 | border-radius: 1.875rem;
28 | font-size: .875rem;
29 | white-space: nowrap;
30 | border: none;
31 | width: 5rem;
32 | height: 2rem;
33 | margin: 0;
34 | pointer-events: auto;
35 | `
36 |
37 | const Line = styled.div`
38 | background: #064497;
39 | height: 2.5rem;
40 | pointer-events: none;
41 | margin: 0 auto;
42 | width: 0.1rem;
43 |
44 | `
45 |
46 | const TimeBarIndicator = () => {
47 | const timer = useSelector(state => state.timer60);
48 | const h24Format = useSelector(state => state.h24);
49 | const date = new Date(timer);
50 | const length = -1+(date.getHours()*17) + (date.getMinutes()*0.25);
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 | )
60 | }
61 |
62 | export default TimeBarIndicator
63 |
--------------------------------------------------------------------------------
/src/components/Vod/PlayerMag.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactNetflixPlayer from "../../other/Player-github/player-github-mag"
3 | import styled from 'styled-components'
4 | import {useParams,useHistory} from "react-router-dom";
5 | import {optimizeName} from "../../other/vod-series-name-optimizer"
6 | import {useSelector} from "react-redux"
7 | import {initStb, stbVolume, stbFullScreen, stbPlayStream, stbPause, stbGetDuration, stbPosition, stbPositionPercent} from "../../other/stb"
8 |
9 | const Container = styled.div`
10 | position:absolute;
11 | width: 100%;
12 | height: 100%;
13 | top: 0;
14 | left: 0;
15 | `
16 |
17 | const PlayerMag = () => {
18 | const {category,stream_id} = useParams();
19 | const history = useHistory();
20 | stbFullScreen(true);
21 | stbVolume(0)
22 |
23 | const stream = useSelector(state => state.playlist).find(x=>x.stream_id === parseInt(stream_id));
24 | stbPlayStream(stream.direct_source)
25 |
26 |
27 | return (
28 |
29 | history.goBack()}
34 | FullPlayer
35 | autoPlay
36 | startPosition={0}
37 | OnEnded={()=> history.goBack()}
38 | overlayEnabled
39 | autoControllCloseEnabled
40 | Style
41 | primaryColor="var(--second-color)"
42 | secundaryColor="var(--first-color)"
43 | />
44 |
45 | )
46 | }
47 |
48 | export default PlayerMag
49 |
--------------------------------------------------------------------------------
/src/components/Live/ChannelEpgBar.js:
--------------------------------------------------------------------------------
1 | import React,{useEffect, useRef } from 'react'
2 | import styled from "styled-components"
3 | import {useSelector} from "react-redux"
4 |
5 |
6 | const ProgressEpg = styled.div`
7 | display: flex;
8 | height: .2rem;
9 | overflow: hidden;
10 | font-size: .75rem;
11 | background-color: black;
12 | `
13 |
14 | const ProgressBar = styled.div`
15 | display: -ms-flexbox;
16 | display: flex;
17 | -ms-flex-direction: column;
18 | flex-direction: column;
19 | -ms-flex-pack: center;
20 | justify-content: center;
21 | overflow: hidden;
22 | color: #fff;
23 | text-align: center;
24 | white-space: nowrap;
25 | transition: width .6s ease;
26 | `
27 |
28 | const ChannelEpgBar = ({start,stop, isPlaying}) => {
29 | const timer = useSelector(state => state.timer5);
30 |
31 | const progress = useRef(0);
32 | const progressEpgStyle = !start ? {
33 | visibility: "hidden",
34 | paddingTop: ".2rem"
35 | } : {};
36 |
37 | const ProgressBarStyle = isPlaying ? {
38 | backgroundColor: "#fff",
39 | border: "1px solid #829196"
40 | } : {
41 | backgroundColor: "#829196"
42 | };
43 | ProgressBarStyle.width = (start ? ((timer-start)/(stop-start))*100 + "%" : 0);
44 |
45 | useEffect(() => {
46 | if(start)
47 | progress.current && (progress.current.style.width = ((timer-start)/(stop-start))*100 + "%");
48 | else progress.current && (progress.current.style.width = 0);
49 | }, [isPlaying,timer,start,stop]);
50 |
51 | return (
52 |
53 |
54 |
55 | )
56 | }
57 |
58 | export default ChannelEpgBar
59 |
--------------------------------------------------------------------------------
/src/components/Vod/GroupRow.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from 'styled-components';
3 | import RowItem from "./RowItem"
4 | import NextArrow from "./NextArrow"
5 | import {Link} from "react-router-dom"
6 |
7 | const Li = styled.li`
8 | color: white;
9 | height: calc(15vw * 1.7 + 1.75rem);
10 | padding-bottom: 1rem;
11 |
12 | & > div{
13 | position:relative;
14 | height: 100%;
15 | width: 100%;
16 | }
17 | `
18 |
19 | const H3 = styled(Link)`
20 | font-size: 1.75rem;
21 | margin-bottom: .5rem;
22 | font-weight: 500;
23 | line-height: 1.2;
24 | margin-top: 0;
25 | color:white;
26 | text-decoration: underline;
27 | text-decoration-color : transparent;
28 | transition: text-decoration-color 0.5s ease;
29 |
30 |
31 | &:focus, &:hover{
32 | color:white;
33 | }
34 | `
35 |
36 |
37 | const maxItem = 7;
38 |
39 | const GroupRow = ({category_id, name, style, playlist, isSeries, existingTmdb}) => {
40 | return (
41 | playlist.length > 0 && (
42 |
43 | {name}
44 |
45 | {playlist.slice(0, maxItem).map((x,id)=>
46 | ()
47 | )}
48 | {playlist.length > maxItem && ()}
49 |
50 |
51 | )
52 | )
53 | }
54 |
55 | export default GroupRow
56 |
--------------------------------------------------------------------------------
/src/components/Live/Tips.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | /*
5 | const Container = styled.div`
6 | position:relative;
7 | bottom:1.5rem;
8 | width:100%;
9 | text-align:center;
10 | color:white;
11 | background-color: var(--first-color);
12 | line-height:1rem;
13 | transform: translate(0%,0%);
14 | `
15 |
16 | const Box = styled.div`
17 | padding: 0 0.3rem;
18 | margin-right: 0.5rem;
19 | `
20 |
21 | const Row = styled.div`
22 | place-content: center;
23 | justify-content: center;
24 | `
25 |
26 | const Column = styled.div`
27 | display: flex;
28 | `
29 | */
30 |
31 | const FloatingRow = styled.div`
32 | bottom: .2rem;
33 | position: absolute;
34 | color: white;
35 | width: 50%;
36 | background-color: var(--first-color);
37 | `
38 |
39 | const Tips = () => {
40 | return (
41 |
42 |
43 |
44 | Detailed TV guide
45 |
46 |
47 |
48 | Category
49 |
50 |
51 |
52 | )
53 | }
54 |
55 | export default Tips
56 |
57 | /*
58 |
59 |
60 |
61 |
62 | Detailed TV guide
63 |
64 |
65 |
66 | Category
67 |
68 |
69 |
70 | */
--------------------------------------------------------------------------------
/src/components/Live/Epg.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from "styled-components"
3 | import PulsingDot from "./PulsingDot"
4 | import {useSelector} from "react-redux"
5 |
6 | const dateFormat = require("dateformat");
7 |
8 | const EpgTitle = styled.div`
9 | padding-left: 1rem!important;
10 | white-space: nowrap;
11 | overflow: hidden;
12 | text-overflow: ellipsis;
13 | max-width: 29vw;
14 | `
15 | const EpgDescription = styled.div`
16 | font-weight: 100;
17 | overflow-wrap: break-word;
18 | overflow: hidden;
19 | /*word-wrap: break-word;
20 | word-break: break-all;*/
21 | text-overflow: ellipsis;
22 | display: -webkit-box;
23 | -webkit-box-orient: vertical;
24 | -webkit-line-clamp: 6;
25 | padding-right: 12px;
26 | `
27 | const EpgList = styled.div`
28 | & > div {
29 | place-self: center;
30 | padding-right: 0;
31 | padding-left: 0;
32 | }
33 | `
34 |
35 |
36 | function EpgRow({title,description,start,stop,first}) {
37 | const h24Format = useSelector(state => state.h24);
38 |
39 | return (
40 |
41 |
42 |
45 |
46 | {dateFormat(new Date(start), h24Format)} - {dateFormat(new Date(stop), h24Format)}
47 |
48 |
49 | {title}
50 |
51 |
52 | {first && (
53 |
54 | {description}
55 |
56 | )}
57 |
58 | )
59 | }
60 |
61 | export default EpgRow
62 |
--------------------------------------------------------------------------------
/src/components/Player/Buttons/PiPButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const ButtonObj = styled.button`
5 | cursor: pointer;
6 | position: relative;
7 | text-align: center;
8 | margin: 0;
9 | padding: 0;
10 | height: 100%;
11 | width: 4em;
12 | -webkit-flex: none;
13 | flex: none;
14 | background: none;
15 | border: none;
16 | color: inherit;
17 | display: inline-block;
18 | font-size: inherit;
19 | line-height: inherit;
20 | text-transform: none;
21 | text-decoration: none;
22 | transition: none;
23 | -webkit-appearance: none;
24 | appearance: none;
25 | transition: text-shadow .2s ease;
26 | margin-left: auto;
27 | &:focus{
28 | outline: 0!important;
29 | }
30 |
31 | &:hover{
32 | text-shadow: 0em 0em 1em white;
33 | }
34 |
35 | `
36 |
37 | const PopupText = styled.span`
38 | border: 0;
39 | clip: rect(0 0 0 0);
40 | height: 1px;
41 | overflow: hidden;
42 | padding: 0;
43 | position: absolute;
44 | width: 1px;
45 | `
46 |
47 | const PiPButton = ({onClick, enabled}) => {
48 | return (
49 |
50 |
51 |
55 |
56 | {!enabled ? "Picture in Picture" : "Exit Picture in Picture"}
57 |
58 | )
59 | }
60 |
61 | export default PiPButton
62 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/ProgrammeListingVIRTUAL.js.BK:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import {useSelector, useDispatch} from "react-redux"
4 | import Programme from "./Programme"
5 | import { VariableSizeList as List } from "react-window";
6 | import {convertRemToPixels} from "../../other/convert-rem"
7 |
8 | const Container = styled.ul`
9 | width: calc(5950px);
10 | display: block;
11 | position: relative;
12 | background-image: url(/img/placeholder-channel-events_border.svg);
13 | background-repeat: repeat-x;
14 | background-size: contain;
15 | height: 6.625rem;
16 | list-style-type:none;
17 | min-width: 357rem;
18 | max-width: 357rem;
19 |
20 | &:before{
21 | content: "";
22 | background-image: url(/img/placeholder-channel-events_border.svg);
23 | background-repeat: repeat-x;
24 | width: 500px;
25 | height: 100%;
26 | position: absolute;
27 | left: -504px;
28 | }
29 | `
30 |
31 | const ProgrammeListing = ({epgId, today, tomorrow, moveFocus}) => {
32 | const epg = useSelector(state => state.epgList);
33 | const epgListing = epg.filter(epg=>epg.id === epgId && epg.start > today && epg.start {
36 | let val = convertRemToPixels(((epgListing[index].end-epgListing[index].start)/3600000)*15)
37 | return val;
38 | }
39 |
40 | const Row = ({ index, style }) => (
41 |
42 | );
43 |
44 | return (
45 |
46 |
53 | {Row}
54 |
55 |
56 | )
57 | }
58 |
59 | export default ProgrammeListing
60 |
--------------------------------------------------------------------------------
/src/other/generate-url.js:
--------------------------------------------------------------------------------
1 | import {getInfo} from "./user_info"
2 | import {getDns, getIsIptveditor} from "./axios"
3 |
4 | export function generateUrl (type, id, extension){
5 | const dns = getIsIptveditor()===true ? process.env.REACT_APP_IPTVEDITOR_API : getDns();
6 | const user = getInfo().username;
7 | const pass = getInfo().password;
8 | return `${dns}${type}/${user}/${pass}/${id}.${ getIsIptveditor()===true ? "mp4" : extension}`
9 | }
10 |
11 | export function convertTsToM3u8(ip) {
12 | let url = ip;
13 | url = url.replace("/live/","/").replace(/\.ts|\.m3u8/g,"")
14 | const splitted = url.split("/");
15 |
16 | const id = splitted[splitted.length-1];
17 | if(isNaN(id))
18 | return ip;
19 | const user = splitted[splitted.length-3];
20 | const pass = splitted[splitted.length-2];
21 | const domain = splitted.slice(0,splitted.length-3).join("/");
22 |
23 | return domain + "/live/" + user + "/" + pass + "/" + id + ".m3u8";
24 | }
25 |
26 | export function catchupUrlGenerator (ip,time, duration){
27 | let url = ip;
28 | time = timeConverter(time)
29 | url = url.replace("/live/","/").replace(/\.ts|\.m3u8/g,"")
30 | const splitted = url.split("/");
31 |
32 | const id = splitted[splitted.length-1];
33 | if(isNaN(id))
34 | return ip;
35 | const user = splitted[splitted.length-3];
36 | const pass = splitted[splitted.length-2];
37 | const domain = splitted.slice(0,splitted.length-3).join("/");
38 |
39 | return domain + "/timeshift/" + user + "/" + pass + "/" + duration + "/" + time.split(":").slice(0,-1).join(":").replace(" ",":") + "/" + id + ".m3u8";
40 | }
41 |
42 | const timeConverter = (time) =>{
43 | const a = new Date(time);
44 | const year = a.getFullYear();
45 | const month = a.getMonth()+1;
46 | const day = a.getDate();
47 | const hour = a.getHours();
48 | const min = a.getMinutes();
49 | const sec = a.getSeconds();
50 |
51 | return year + "-" + (month < 10 ? "0"+month : month) + "-" + (day < 10 ? "0"+day : day) + " " + (hour < 10 ? "0"+hour : hour) + ':' + (min < 10 ? "0"+min : min) + ':' + (sec < 10 ? "0"+sec : sec);
52 | };
53 |
54 | export default {generateUrl, catchupUrlGenerator}
55 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | IPTVEditor Web Player
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/other/local-db.js:
--------------------------------------------------------------------------------
1 | import {getInfo} from "./user_info"
2 |
3 | let history = {live:[], movie:[], series:[]};
4 | let favorite = {live:[], movie:[], series:[]};
5 |
6 | export const initDb = () =>{
7 | let txt = localStorage.getItem("history-" + getInfo().username);
8 | txt ? (history = JSON.parse(txt)) : saveDB(0);
9 |
10 | txt = localStorage.getItem("favorite-" + getInfo().username);
11 | txt ? (favorite = JSON.parse(txt)) : saveDB(1);
12 | }
13 |
14 | const findOne = (type, key, fav) =>{
15 | const db = getDb(fav);
16 | const el = db[type].find(x=>parseInt(x.id) === parseInt(key))
17 | if(!el)
18 | return null;
19 | return el && fav !== true ? {...el, date: new Date(el.date)} : el;
20 | }
21 |
22 | const findAll = (type, fav) =>{
23 | if(fav !== true)
24 | return history[type].sort((a,b)=> a.date-b.date).map(x=> {return {...x, date : new Date(x.date)}});
25 | else return favorite[type];
26 | }
27 |
28 | const set = (type,key,value, fav) =>{
29 | const db = getDb(fav);
30 |
31 | fav !== true && (value = {...value, date: Date.now()})
32 |
33 | const i = db[type].findIndex(x=>x.id === key);
34 | i !== -1 ? db[type][i] = value : db[type].unshift(value)
35 |
36 | reorderDB(fav);
37 | saveDB(fav);
38 | }
39 |
40 | const del = (type, key, fav) =>{
41 | if(fav !== true)
42 | history[type] = history[type].filter(x=> parseInt(x.id) !== parseInt(key));
43 | else favorite[type] = favorite[type].filter(x=> parseInt(x.id) !== parseInt(key));
44 |
45 | saveDB(fav);
46 | }
47 |
48 | const reorderDB = (fav) =>{
49 | if (fav !== true) {
50 | history.live = history.live.slice(0, 20).sort((a, b) => b.date - a.date)
51 | history.movie = history.movie.slice(0, 20).sort((a, b) => b.date - a.date)
52 | history.series = history.series.slice(0, 20).sort((a, b) => b.date - a.date)
53 | }else{
54 | favorite.live = favorite.live.slice(0, 30)
55 | favorite.movie = favorite.movie.slice(0, 30)
56 | favorite.series = favorite.series.slice(0, 30)
57 | }
58 | }
59 |
60 | const saveDB = (fav) =>{
61 | window.localStorage.setItem((fav !== true ? "history-" : "favorite-") + getInfo().username, JSON.stringify(fav !== true ? history : favorite));
62 | }
63 |
64 | const getDb = fav => fav !== true ? history : favorite
65 |
66 | export default {findOne, findAll, set, del}
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @media (max-width: 1000px) {
2 | /*MAG*/
3 | html {
4 | font-size: 62.5%;
5 | }
6 | }
7 |
8 | html {
9 | font-family: "Proxima Nova", sans-serif;
10 | overflow: hidden;
11 | }
12 |
13 | * {
14 | scrollbar-color: #aaa transparent;
15 | scrollbar-width: thin;
16 | }
17 |
18 | *::-webkit-scrollbar {
19 | width: 12px;
20 | border-radius: 6px;
21 | }
22 |
23 | *::-webkit-scrollbar-track {
24 | border-radius: 6px;
25 | background-color: transparent;
26 | }
27 |
28 | *::-webkit-scrollbar-thumb {
29 | background-clip: padding-box;
30 | background-color: #aaa;
31 | border: 2px solid transparent;
32 | min-height: 50px;
33 | border-radius: 6px;
34 | }
35 |
36 |
37 | body {
38 | width: 100vw;
39 | height: 100vh;
40 | background-color: #fff0;
41 | max-width: 100vw;
42 | }
43 |
44 | .alert-enter {
45 | opacity: 0;
46 | transform: scale(0.9);
47 | }
48 |
49 | .alert-enter-active {
50 | opacity: 0;
51 | transform: translateX(0);
52 | transition: opacity 300ms, transform 300ms;
53 | }
54 |
55 | .alert-exit {
56 | opacity: 0;
57 | }
58 |
59 | .alert-exit-active {
60 | opacity: 0;
61 | transform: scale(0.9);
62 | transition: opacity 300ms, transform 300ms;
63 | }
64 |
65 |
66 | .isPlayingChannel {
67 | color: #fff;
68 | }
69 |
70 | .isPlayingChannel:hover {
71 | color: #000;
72 | cursor: pointer;
73 | }
74 |
75 | .isPlayingChannel:hover * {
76 | color: #000;
77 | cursor: pointer;
78 | }
79 |
80 | #selectedCh * {
81 | color: white;
82 | }
83 |
84 | .hg-keyMarker{
85 | box-shadow: 0 0 0 2px var(--second-color) !important;
86 | }
87 |
88 |
89 |
90 | .alert-enter {
91 | opacity: 0;
92 | transform: scale(0.9);
93 | }
94 | .alert-enter-active {
95 | opacity: 1;
96 | transform: translateX(0);
97 | transition: opacity 300ms, transform 300ms;
98 | }
99 | .alert-exit {
100 | opacity: 1;
101 | }
102 | .alert-exit-active {
103 | opacity: 0;
104 | transform: scale(0.9);
105 | transition: opacity 300ms, transform 300ms;
106 | }
107 |
108 |
109 | .center-container{
110 | display: inline-flex;
111 | align-content: center;
112 | justify-content: center;
113 | align-items: center;
114 | }
115 |
116 | body > iframe{
117 | display: none;
118 | }
--------------------------------------------------------------------------------
/src/other/key-navigation.js:
--------------------------------------------------------------------------------
1 | let comands = {};
2 | let toFocusName = null;
3 |
4 | export function setComands(comand){
5 | document.removeEventListener("keydown",navigation)
6 | document.addEventListener("keydown", navigation)
7 | comands = comand;
8 | toFocusName = {parent:comand.parent, child:comand.child};
9 | comands.focus();
10 | }
11 |
12 |
13 | export function navigation(event, comands){
14 | event.preventDefault();
15 | event.stopImmediatePropagation();
16 | if (event.keyCode === 39) { //right
17 | comands.right && (comands.right());
18 | } else if(event.keyCode === 37){ //left
19 | comands.left && (comands.left());
20 | } else if(event.keyCode === 40){ //down
21 | comands.down && (comands.down());
22 | } else if(event.keyCode === 38){ //up
23 | comands.up && (comands.up());
24 | } else if(event.keyCode === 107 || event.keyCode === 109){ //volume
25 | comands.volume && (comands.volume(event.keyCode === 107 || event.keyCode === 109));
26 | } else if(event.keyCode === 27 || event.keyCode === 8){ //back
27 | comands.back && (comands.back());
28 | }
29 | }
30 |
31 | export function findParentBySelector(elm, selector) {
32 | var all = document.querySelectorAll(selector);
33 | var cur = elm.parentNode;
34 | while(cur && !collectionHas(all, cur)) { //keep going up until you find a match
35 | cur = cur.parentNode; //go up
36 | }
37 | return cur; //will return null if not found
38 | }
39 |
40 |
41 | function collectionHas(a, b) { //helper function (see below)
42 | for(var i = 0, len = a.length; i < len; i ++) {
43 | if(a[i] === b) return true;
44 | }
45 | return false;
46 | }
47 |
48 | export function nextInput(currentInput, inputArray, inputClass, reverse) {
49 | inputArray = Array.from(inputArray)
50 | let i,j, array = reverse ? inputArray.reverse() : inputArray;
51 | for (i = 0; i < inputArray.length - 1; i++) {
52 | if(currentInput === array[i]) {
53 | for (j = 1; j < inputArray.length - i; j++) {
54 | //Check if the next element exists and if it has the desired class
55 | if(array[i + j] && (array[i + j].className === inputClass)) {
56 | return array[i + j];
57 | break;
58 | }
59 | }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/EpgChannel.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import ProgrammeListing from "./ProgrammeListing"
4 |
5 | const Container = styled.div`
6 | display: block;
7 | margin-top: 1rem;
8 | `
9 |
10 | const Channel = styled.div`
11 | display: inline-block;
12 | z-index: 2;
13 | padding-bottom: .5rem;
14 | position: -webkit-sticky;
15 | position: sticky;
16 | left: 160px;
17 | z-index: 1;
18 | line-height:.4rem;
19 | transition: all 0.1s ease 0s;
20 |
21 | & > button{
22 | box-sizing: border-box;
23 | border: 1px solid #e5e5e5;
24 | border-radius: .75rem;
25 | background-color: #fff;
26 | display: flex;
27 | flex-flow: row nowrap;
28 | align-items: center;
29 | margin-bottom: 0;
30 | text-decoration: none;
31 | color: #303030;
32 | }
33 |
34 | & > button > p{
35 | padding-right: 1rem;
36 | margin-top: revert;
37 | margin-bottom: revert;
38 | }
39 | `
40 |
41 | const ChannelLogo = styled.div`
42 | padding-right: .5rem;
43 | margin-right: .5rem;
44 | padding-left: .5rem;
45 | color: #fff;
46 | border-right: .1rem solid #e5e5e5;
47 |
48 | & > img{
49 | display: block;
50 | height: 100%;
51 | max-height: 1.625rem;
52 | width: auto;
53 | max-width: 100%;
54 | }
55 | `
56 |
57 |
58 |
59 | const EpgChannel = ({Name,Image, Epg, Shift, day, moveFocus, style, leftScroll, chId, catchup}) => {
60 | let today = new Date()
61 | today.setDate(today.getDate()+day);
62 | today.setHours(0)
63 | today.setMinutes(0)
64 | today.setSeconds(0)
65 | today.setMilliseconds(0)
66 | let tomorrow = new Date(today);
67 | tomorrow.setDate(tomorrow.getDate()+1)
68 |
69 | return (
70 |
71 |
72 |
78 |
79 |
80 |
81 | )
82 | }
83 |
84 | export default EpgChannel
85 |
--------------------------------------------------------------------------------
/src/other/vod-series-name-optimizer.js:
--------------------------------------------------------------------------------
1 | export function optimizeName(name) {
2 | name = name.replace("\r", "").replace(/(-\s*\d{2,4})|vod|fhd|hd|360p|4k|h264|h265|24fps|60fps|720p|1080p|vod|x264|x265|\.avi|\.mp4|\.mkv|\[.*]|\(.*\)|\{.*\}|-|_|\./gim, " ").replace(/(- \d\d\d\d$)/, "");
3 | name = name.replace(" ", " ");
4 | name = name.replace(" ", " ");
5 | return name.trim();
6 | }
7 |
8 | export function secondsToHms(d) {
9 | d = Number(d);
10 | var h = Math.floor(d / 3600);
11 | var m = Math.floor(d % 3600 / 60);
12 | var s = Math.floor(d % 3600 % 60);
13 | return `${h}:${m < 10 ? `0${m}` : m}:${s < 10 ? `0${s}` : s}`;
14 | }
15 |
16 | export function createMovieInfo(name, image, tmdbId = -1, releaseDate = "", coverBig = "", youtubeTrailer = "", director = "", actors = "", cast = "", description = "", plot = "", age = "", country = "", genre = "", backdropPath = "", durationSecs = 0, duration = "", rating10 = 0, status = "Released", runtime = 0) {
17 | return {
18 | "info": {
19 | "name": name,
20 | "tmdb_id": tmdbId,
21 | "cover_big": coverBig,
22 | "movie_image": image,
23 | "releasedate": releaseDate,
24 | "youtube_trailer": youtubeTrailer,
25 | "director": director,
26 | "actors": actors,
27 | "cast": cast,
28 | "description": description,
29 | "plot": plot,
30 | "age": age,
31 | "country": country,
32 | "genre": genre,
33 | "backdrop_path": [
34 | backdropPath
35 | ],
36 | "duration_secs": durationSecs,
37 | "duration": duration,
38 | "rating": rating10,
39 | "status": status,
40 | "runtime": runtime
41 | },
42 | }
43 | }
44 |
45 | export function createEpisode(url, id, season, episode_number, duration_sec = 0, duration = "", title = "", airdate = "", crew = "", rating = "0", image = "", overview) {
46 | return { //season
47 | "episode_num": episode_number,
48 | "title": title === "" ? `Episode ${episode_number}` : title,
49 | "container_extension": "mp4",
50 | "url": url,
51 | "id": id,
52 | "overview": overview,
53 | "info": {
54 | "air_date": airdate,
55 | "crew": crew,
56 | "rating": parseInt(rating),
57 | "id": id,
58 | "movie_image": image,
59 | "duration_secs": duration_sec,
60 | "duration": duration,
61 | },
62 | "season": season,
63 | }
64 | }
--------------------------------------------------------------------------------
/src/components/Vod/PlayerVod.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react'
2 | import ReactNetflixPlayer from "../../other/Player-github/player-github"
3 | import styled from 'styled-components'
4 | import {useParams,useHistory} from "react-router-dom";
5 | import {optimizeName} from "../../other/vod-series-name-optimizer"
6 | import {useSelector} from "react-redux"
7 | import DB from "../../other/local-db"
8 | import {generateUrl} from "../../other/generate-url"
9 |
10 | const Container = styled.div`
11 | position:absolute;
12 | width: 100%;
13 | height: 100%;
14 | top: 0;
15 | left: 0;
16 | `
17 |
18 | const PlayerVod = () => {
19 | const {category,stream_id} = useParams();
20 | const history = useHistory();
21 |
22 | const stream = useSelector(state => state.playlist).find(x=>x.stream_id === parseInt(stream_id));
23 | const [streamStat,setStreamStat] = useState(DB.findOne("movie",stream_id));
24 |
25 | const [url, setUrl] = useState();
26 |
27 | useEffect(()=>{
28 | if(!stream){
29 | history.replace(`/movie/`);
30 | return;
31 | }
32 | if(stream.direct_source)
33 | setUrl(stream.direct_source)
34 | else{
35 | setUrl(generateUrl("movie",stream.stream_id,stream.container_extension))
36 | }
37 | },[stream])
38 |
39 | useEffect(()=>{
40 | let stat = DB.findOne("movie",stream_id);
41 | !stat && (stat={id: stream_id, start:0, tot:0})
42 | setStreamStat(stat)
43 | DB.set("movie",stream_id, stat)
44 | },[category, stream_id])
45 |
46 | const setStat = (stat) =>{
47 | setStreamStat(stat);
48 | DB.set("movie",stream_id, stat)
49 | }
50 |
51 |
52 | return (
53 |
54 | history.goBack()}
59 | FullPlayer
60 | autoPlay
61 | startPosition={streamStat ? parseInt(streamStat.start) : 0}
62 | syncDuration = {(duration, percentage) => setStat({...streamStat, start:duration, tot : parseInt(percentage)})}
63 |
64 | OnEnded={()=> {DB.del("movie",stream_id);history.goBack()}}
65 | overlayEnabled
66 | autoControllCloseEnabled
67 | Style
68 | primaryColor="var(--second-color)"
69 | secundaryColor="var(--first-color)"
70 | />
71 |
72 | )
73 | }
74 |
75 | export default PlayerVod
76 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/ProgrammeListing.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react'
2 | import styled from 'styled-components'
3 | import Programme from "./Programme"
4 | import ProgrammeFake from "./Programme-fake"
5 | import {getEpg, downloadEpgData} from "../../other/epg-database"
6 |
7 | const Container = styled.ul`
8 | width: calc(408rem);
9 | display: block;
10 | position: relative;
11 | background-image: url(/img/placeholder-channel-events_border.svg);
12 | background-repeat: repeat-x;
13 | background-size: contain;
14 | height: 6.625rem;
15 | list-style-type:none;
16 | min-width: 408rem;
17 | max-width: 408rem;
18 |
19 | &:before{
20 | content: "";
21 | background-image: url(/img/placeholder-channel-events_border.svg);
22 | background-repeat: repeat-x;
23 | width: 500px;
24 | height: 100%;
25 | position: absolute;
26 | left: -504px;
27 | }
28 | `
29 |
30 | const ProgrammeListing = ({Epg, Shift, today, moveFocus,chId, day, liveTime, catchup}) => {
31 | const [epgListing, setEpgListing] = useState([]);
32 | const catchupDate = catchup > 0 ? Date.now() - (catchup * 1000 * 60 * 60 * 24) : 0
33 |
34 | useEffect(() => {
35 | async function fetchData(d) {
36 | if (!Epg)
37 | return;
38 | await downloadEpgData(chId, Epg, d+1, Shift);
39 |
40 | const newEpg = getEpg(Epg, d+1, Shift);
41 | if (newEpg) {
42 | setEpgListing([...newEpg]);
43 | } else setEpgListing([]);
44 | }
45 | fetchData(day);
46 | }, [day])
47 |
48 |
49 |
50 | return (
51 |
52 | {epgListing.length === 0 ?
53 | fakeEpgGenerator(today).map((epg) => (
54 | ()
55 | ))
56 | :
57 | epgListing.map((epg,id) => (
58 | 0 && epg.start >= catchupDate && epg.start <= Date.now()}/>
59 | ))
60 | }
61 |
62 | )
63 | }
64 |
65 | export default ProgrammeListing
66 |
67 |
68 | const fakeEpgGenerator = (today) =>{
69 | let toReturn = [];
70 | for(let i = 0; i < 24; i+=3){
71 | toReturn.push({start: today+(i*3600000), end: today+((i+3)*3600000)})
72 | }
73 | return toReturn;
74 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Streamity.tv v2 Xtream IPTV Player (by IPTVEditor.com)
2 |
3 | 
4 |
5 | [](https://github.com/lKinderBueno/StreamityTV-Xtream)
6 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CVT6HXLZ3YNSG&source=url)
7 |
8 |
9 | ### Official Streamity.tv website: [Click here](https://streamity.tv)
10 |
11 | Streamity is an online webplayer. Watch IPTV Channels, movies TV series online on your browser with no additional software required!
12 |
13 | ### DISCLAIMER: Streamity is a player. No stream or movies is included. Pictures are just for demo purpouse.
14 |
15 | ### Features
16 | - Xtream Api support
17 | - Customizable name and logo
18 | - Modern design and smooth animations
19 | - EPG Viewer
20 | - 12h/24h EPG time format support
21 | - Pic in picture player
22 | - Automatic fix and improve movie and series name
23 | - Favorites
24 | - Continue to watch for vod and series
25 | - Automatic select next series episode
26 | - React js
27 |
28 | ### Other Pictures
29 | 
30 | 
31 |
32 |
33 | ### Installation
34 | 1. Download latest release (streamity-v2.X.zip): [Click here](https://github.com/lKinderBueno/Streamity-Xtream-IPTV-Web-player/releases)
35 | 2. Open with a text editor (for example notepad++) config.js and complete empty fields ( window.dns). More instructions are available inside the file.
36 | 3. Open with a text editor (for example notepad++) config.php and write your mysql database info (database url, database name, username, password) and epg xml url. (if you don't use epg you can skip part 2 and 3)
37 | 4. Import "sql_table.sql" in your mysql database (if you are using phpMyAdmin click on "import" -> select the sql_table.sql and click on execute)
38 |
39 | 5. [OPTIONAL] Open with a text editor (for example notepad++) config.css if you want to change main color and background one.
40 | 6. [OPTIONAL] Change favicon.ico and img > banner_w.png
41 |
42 | 7. Copy and paste all the files in your server. Use the root folder, like http://domain.com/ (don't use folders like http://domain.com/player/ !)
43 |
44 | ### Installation in sub-folder (ex: http://domain.com/player/ )
45 | Copy the "static" folder in your root folder and all the other files in /player/
46 |
47 |
48 |
49 |
50 | To avoid wasting server resources, epg update will be triggered when an user login and database has less than 12 hours of programmes.
51 |
--------------------------------------------------------------------------------
/src/other/axios.js:
--------------------------------------------------------------------------------
1 | import Axios from "axios";
2 | import Cookies from 'js-cookie'
3 | import qs from "qs"
4 |
5 | let dns = null;
6 | let proxyRequired = window.cors;
7 | let isIptveditor = null;
8 |
9 | setDns(window.dns);
10 |
11 | export async function get(url, timeout = 1) {
12 | return Axios.get(url).catch(err => err);
13 | }
14 |
15 | export async function post(url, params = {}, local, useProxy) {
16 | if (!dns)
17 | return null;
18 |
19 | let uri = url
20 |
21 | if (local === true && !window.location.origin.match(/iptveditor\.com|localhost/)) {
22 | return Axios.post(uri, qs.stringify(params)).catch(err => err)
23 | }
24 |
25 | if (isIptveditor === false) {
26 | uri += "?";
27 | for (const key in params) {
28 | uri += key + "=" + encodeURIComponent(params[key]) + "&";
29 | }
30 | }
31 |
32 | if ((proxyRequired || useProxy) === true && isIptveditor === true)
33 | uri = "/proxy.php?url=" + encodeURIComponent(dns);
34 | else if ((proxyRequired || useProxy) === true && isIptveditor === false)
35 | uri = "/proxy.php?url=" + encodeURIComponent(dns + uri);
36 | else if (isIptveditor === false)
37 | uri = dns + uri;
38 | else uri = dns;
39 |
40 | return isIptveditor === true && !(proxyRequired || useProxy)?
41 | Axios.post(uri, qs.stringify(params)) :
42 | Axios.get(uri, {
43 | headers: {
44 | 'User-Agent': 'Mozilla/6.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.55 Safari/537.36 Edge/86.0.622.28'
45 | },
46 | timeout: 25000
47 | }).catch(err => {
48 | if(proxyRequired === false && !useProxy && !err.response)
49 | return post(url, params, local, true)
50 | return err
51 | });
52 |
53 | }
54 |
55 |
56 | export function setDns(data) {
57 | if (!data)
58 | return;
59 | if (window.location.protocol !== 'https:' && data.includes("https"))
60 | data = data.replace("https", "http");
61 | else if (window.location.protocol === 'https:' && !data.includes("https"))
62 | data = data.replace("http", "https");
63 |
64 | isIptveditor = !!data.match(/iptveditor\.com|xtream-ie|opop\.pro|localhost|192\.168\.178\.71\:3100/)
65 |
66 | if (window.isDebug === 1)
67 | data = window.dns;
68 | else if (isIptveditor === true && window.isDebug !== 1)
69 | data = `${process.env.REACT_APP_IPTVEDITOR_API}webplayer`;
70 | else {
71 | if (data[data.length - 1] !== "/")
72 | data += "/";
73 | }
74 |
75 | dns = data;
76 | Cookies.set("dns", data, { expires: 365 })
77 | }
78 |
79 | export function getDns() {
80 | return dns;
81 | }
82 |
83 | export function getIsIptveditor() {
84 | return isIptveditor;
85 | }
--------------------------------------------------------------------------------
/src/components/Live/BLUE/ChannelEpgBLUE.js_OLD:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import {useSelector} from "react-redux"
3 | import { useState, useEffect } from 'react'
4 | import {getEpg} from "../../other/getEpg"
5 | import ChannelEpgBar from "./ChannelEpgBar"
6 | const dateFormat = require("dateformat");
7 | const HeaderChannel = styled.div`
8 | padding-bottom: .4rem!important;
9 | line-height: 1!important;
10 | font-size: 0.65rem;
11 | color: #598ede;
12 | font-weight: 700;
13 | `
14 |
15 | const BodyChannel = styled.div`
16 | text-transform: capitalize;
17 | color: #fff;
18 | overflow: hidden;
19 | white-space: nowrap;
20 | text-overflow: ellipsis;
21 | `
22 | const Title = styled.label`
23 | font-size: 1rem;
24 | max-width: 100%;
25 | white-space: nowrap;
26 | overflow: hidden;
27 | text-overflow: ellipsis;`
28 |
29 | const TitleEpg = styled.label`
30 | padding-left: 2%;
31 | font-size: 0.9rem;
32 | font-weight: 100;
33 | max-width: 70%;
34 | white-space: nowrap;
35 | overflow: hidden;
36 | text-overflow: ellipsis;`
37 |
38 | const PlayingButton = styled.i`
39 | font-size: 2.2rem;
40 | color: #e9d454;
41 | `
42 |
43 | const ChannelEpg = ({chId,Name,Epg,isPlaying}) => {
44 | const epg = useSelector(state => state.epgList);
45 | const h24Format = useSelector(state => state.h24);
46 | const timer = useSelector(state => state.timer60);
47 | const [epgNow, setEpgNow] = useState(getEpg(epg,Epg));
48 |
49 | let tomorrow = new Date()
50 | tomorrow.setDate(tomorrow.getDate()+1)
51 | tomorrow.setHours(0)
52 | tomorrow.setMinutes(0)
53 |
54 | useEffect(() => {
55 | const newEpg = getEpg(epg,Epg);
56 | if(epgNow.start && epgNow.start !== newEpg.start){
57 | setEpgNow({...newEpg});
58 | }
59 | }, [timer,epgNow.start,epg,Epg]);
60 |
61 | return (
62 | <>
63 |
64 |
65 | {epgNow.start && `${dateFormat(new Date(epgNow.start), h24Format)} - ${dateFormat(new Date(epgNow.end), h24Format)}`}
66 |
67 |
68 | {Name}
69 | {epgNow.title}
70 |
71 |
72 |
73 | {isPlaying && (
)}
74 |
75 |
76 | {epgNow.start ?
77 | ()
78 | :
79 |
80 | }
81 |
82 | >
83 | )
84 | }
85 |
86 | export default ChannelEpg
87 |
88 |
--------------------------------------------------------------------------------
/src/components/Group/Groups.css:
--------------------------------------------------------------------------------
1 | .netflix-text {
2 | text-transform: uppercase;
3 | }
4 |
5 | .netflix-nav-rev-btn {
6 | border: 0;
7 | background: transparent;
8 | cursor: pointer;
9 | font-size: 1.5rem;
10 | }
11 |
12 |
13 | .netflix-nav-rev {
14 | position: fixed;
15 | top: 0;
16 | right: -5%;
17 | height: 100vh;
18 | transform: translateX(100%);
19 | transition: transform .3s ease-in-out;
20 | z-index: 1000000000;
21 | }
22 |
23 | .netflix-nav-rev.visible {
24 | transform: translateX(0);
25 | }
26 |
27 | .netflix-nav-rev-black {
28 | background-color: var(--second-color);
29 | width: 60%;
30 | max-width: 35rem;
31 | min-width: 20rem;
32 | transition-delay: .4s;
33 | }
34 |
35 | .netflix-nav-rev-black.visible {
36 | transition-delay: 0s;
37 | }
38 |
39 | .netflix-nav-rev-red {
40 | background-color: var(--first-color);
41 | transition-delay: .2s;
42 | width: 95%;
43 | }
44 |
45 | .netflix-nav-rev-red.visible {
46 | transition-delay: .2s;
47 | }
48 |
49 | .netflix-nav-rev-white {
50 | background-color: #fff;
51 | padding: 3rem 0rem 0rem 2rem;
52 | position: relative;
53 | transition-delay: 0s;
54 | width: 95%;
55 | }
56 |
57 | .netflix-nav-rev-white.visible {
58 | transition-delay: .4s;
59 | }
60 |
61 | .netflix-close-btn-reverse {
62 | position: unset;
63 | }
64 |
65 | .netflix-close-btn:focus, .netflix-close-btn:hover, .netflix-close-btn:hover > i {
66 | color: var(--second-color);
67 | outline-width: 0px;
68 | }
69 |
70 | .netflix-logo {
71 | width: 10rem;
72 | }
73 |
74 | .netflix-list {
75 | list-style-type: none;
76 | padding: 0;
77 | height: 100%;
78 | overflow: auto;
79 | }
80 |
81 | .netflix-list-reverse {
82 | margin-right: 8rem;
83 |
84 | }
85 |
86 | .netflix-list-reverse li {
87 | margin: 1rem 0;
88 | padding-left: 0.5rem;
89 | }
90 |
91 | .netflix-list li button {
92 | color: var(--first-color);
93 | font-size: 1rem;
94 | text-decoration: none;
95 | text-transform: uppercase;
96 | transition: color 0.2s ease;
97 | }
98 |
99 | .netflix-list li button:hover, .netflix-list li button:focus{
100 | color: var(--second-color);
101 | }
102 |
103 |
104 | .netflix-list ul {
105 | list-style-type: none;
106 | padding-left: 1.4rem;
107 | }
108 |
109 |
110 |
111 | .floating-btn {
112 | border-radius: 26.1rem;
113 | background-color: #001F61;
114 | border: .1rem solid #001F61;
115 | box-shadow: 0 1.3rem 1.5rem -1.6rem #03153B;
116 | color: #fff;
117 | cursor: pointer;
118 | font-size: 1.3rem;
119 | line-height: 1.4rem;
120 | padding: 1.2rem 1.4rem;
121 | position: fixed;
122 | bottom: 1.4rem;
123 | right: 1.4rem;
124 | z-index: 999;
125 | }
126 |
127 | .floating-btn:hover {
128 | background-color: #ffffff;
129 | color: #001F61;
130 | }
131 |
132 | .floating-btn:focus {
133 | outline: none;
134 | }
135 |
136 | @media screen and (max-width: 44rem) {
137 |
138 | .social-panel-container.visible {
139 | transform: translateX(0);
140 | }
141 |
142 | .floating-btn {
143 | right: 1rem;
144 | }
145 | }
146 |
147 | * {
148 | box-sizing: border-box;
149 | }
--------------------------------------------------------------------------------
/src/components/LateralBar/LateralBar.css:
--------------------------------------------------------------------------------
1 | .netflix-text {
2 | text-transform: uppercase;
3 | }
4 |
5 | .lateral-focus, .lateral-focus:focus{
6 | background-color: transparent;
7 | border: none;
8 | outline: none;
9 |
10 | }
11 |
12 | .netflix-nav-btn {
13 | border: 0;
14 | background: transparent;
15 | cursor: pointer;
16 | font-size: 1.5rem;
17 | }
18 |
19 |
20 | .netflix-nav {
21 | position: fixed;
22 | top: 0;
23 | left: 0;
24 | height: 100vh;
25 | transform: translateX(-100%);
26 | transition: transform .3s ease-in-out;
27 | z-index: 1000000000;
28 | }
29 |
30 | .netflix-nav.visible {
31 | transform: translateX(0);
32 | }
33 |
34 | .netflix-nav-black {
35 | background-color: var(--first-color);
36 | width: 60%;
37 | max-width: 35rem;
38 | min-width: 20rem;
39 | transition-delay: .4s;
40 | }
41 |
42 | .netflix-nav-black.visible {
43 | transition-delay: 0s;
44 | }
45 |
46 | .netflix-nav-red {
47 | background-color: var(--second-color);
48 | transition-delay: .2s;
49 | width: 95%;
50 | }
51 |
52 | .netflix-nav-red.visible {
53 | transition-delay: .2s;
54 | }
55 |
56 | .netflix-nav-white {
57 | background-color: #fff;
58 | padding: 3rem;
59 | position: relative;
60 | transition-delay: 0s;
61 | width: 95%;
62 | }
63 |
64 | .netflix-nav-white.visible {
65 | transition-delay: .4s;
66 | }
67 |
68 | .netflix-close-btn {
69 | outline-width: 0px;
70 | opacity: 0.3;
71 | position: absolute;
72 | top: 3rem;
73 | right: 2.5rem;
74 | transition: color 0.5s ease;
75 | }
76 |
77 | .netflix-close-btn:focus, .netflix-close-btn:hover, .netflix-close-btn:hover > i {
78 | color: var(--second-color);
79 | }
80 |
81 | .netflix-logo {
82 | width: 10rem;
83 | }
84 |
85 | .netflix-list {
86 | list-style-type: none;
87 | padding: 0;
88 | }
89 |
90 | .netflix-list li {
91 | margin: 1.5rem 0;
92 | }
93 |
94 | .netflix-list li button {
95 | color: var(--first-color);
96 | font-size: 1rem;
97 | text-decoration: none;
98 | text-transform: uppercase;
99 | transition: color 0.2s ease;
100 | }
101 |
102 | .netflix-list li button:hover, .netflix-list li button:focus{
103 | color: var(--second-color);
104 | }
105 |
106 |
107 | .netflix-list ul {
108 | list-style-type: none;
109 | padding-left: 1.4rem;
110 | }
111 |
112 |
113 |
114 | .floating-btn {
115 | border-radius: 26.1rem;
116 | background-color: #001F61;
117 | border: .1rem solid #001F61;
118 | box-shadow: 0 1.3rem 1.5rem -1.6rem #03153B;
119 | color: #fff;
120 | cursor: pointer;
121 | font-size: 1.3rem;
122 | line-height: 1.4rem;
123 | padding: 1.2rem 1.4rem;
124 | position: fixed;
125 | bottom: 1.4rem;
126 | right: 1.4rem;
127 | z-index: 999;
128 | }
129 |
130 | .floating-btn:hover {
131 | background-color: #ffffff;
132 | color: #001F61;
133 | }
134 |
135 | .floating-btn:focus {
136 | outline: none;
137 | }
138 |
139 | @media screen and (max-width: 44rem) {
140 |
141 | .social-panel-container.visible {
142 | transform: translateX(0);
143 | }
144 |
145 | .floating-btn {
146 | right: 1rem;
147 | }
148 | }
149 |
150 | * {
151 | box-sizing: border-box;
152 | }
--------------------------------------------------------------------------------
/src/other/Transition.css:
--------------------------------------------------------------------------------
1 | .hvr-reveal {
2 | display: inline-block;
3 | vertical-align: middle;
4 | -webkit-transform: perspective(1px) translateZ(0);
5 | transform: perspective(1px) translateZ(0);
6 | box-shadow: 0 0 1px rgba(0, 0, 0, 0);
7 | position: relative;
8 | overflow: hidden;
9 | }
10 |
11 | .hvr-reveal:before,
12 | .hvr-reveal:after {
13 | content: "";
14 | position: absolute;
15 | z-index: -1;
16 | left: 0;
17 | right: 0;
18 | top: 0;
19 | bottom: 0;
20 | border-color: var(--second-color);
21 | border-style: solid;
22 | border-width: 0;
23 | -webkit-transition-property: border-width;
24 | transition-property: border-width;
25 | -webkit-transition-duration: 0.1s;
26 | transition-duration: 0.1s;
27 | -webkit-transition-timing-function: ease-out;
28 | transition-timing-function: ease-out;
29 | }
30 |
31 | .hvr-reveal:hover:before,
32 | .hvr-reveal:focus:before,
33 | .hvr-reveal:active:before {
34 | -webkit-transform: translateY(0);
35 | transform: translateY(0);
36 | border-width: 4px;
37 | }
38 |
39 | .hvr-grow-shadow {
40 | display: inline-block;
41 | vertical-align: middle;
42 | -webkit-transform: perspective(1px) translateZ(0);
43 | transform: perspective(1px) translateZ(0);
44 | box-shadow: 0 0 1px rgba(0, 0, 0, 0);
45 | -webkit-transition-duration: 0.3s;
46 | transition-duration: 0.3s;
47 | -webkit-transition-property: box-shadow, transform;
48 | transition-property: box-shadow, transform;
49 | }
50 | .hvr-grow-shadow:hover, .hvr-grow-shadow:focus, .hvr-grow-shadow:active {
51 | box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.5);
52 | -webkit-transform: scale(1.1);
53 | transform: scale(1.1);
54 | }
55 |
56 |
57 |
58 | /* Shutter In Horizontal */
59 | .hvr-shutter-in-horizontal {
60 | display: inline-block;
61 | vertical-align: middle;
62 | -webkit-transform: perspective(1px) translateZ(0);
63 | transform: perspective(1px) translateZ(0);
64 | box-shadow: 0 0 1px rgba(0, 0, 0, 0);
65 | position: relative;
66 | background: white;
67 | -webkit-transition-property: color;
68 | transition-property: color;
69 | -webkit-transition-duration: 0.3s;
70 | transition-duration: 0.3s;
71 | transition: all 0.3s ease-out;
72 |
73 | }
74 |
75 |
76 | .hvr-shutter-in-horizontal:before {
77 | content: "";
78 | position: absolute;
79 | z-index: -1;
80 | top: 0;
81 | bottom: 0;
82 | left: 0;
83 | right: 0;
84 | background: var(--first-color);
85 | -webkit-transform: scaleX(1);
86 | transform: scaleX(1);
87 | -webkit-transform-origin: 50%;
88 | transform-origin: 50%;
89 | -webkit-transition-property: transform;
90 | transition-property: transform;
91 | -webkit-transition-duration: 0.3s;
92 | transition-duration: 0.3s;
93 | -webkit-transition-timing-function: ease-out;
94 | transition-timing-function: ease-out;
95 | }
96 |
97 |
98 | .hvr-shutter-in-horizontal-on *{
99 | color:black !important;
100 | }
101 |
102 | .hvr-shutter-in-horizontal-on:before {
103 | -webkit-transform: scaleX(0);
104 | transform: scaleX(0);
105 | }
106 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react"
2 | import {HashRouter as Router, Route,Switch} from "react-router-dom"
3 | import {PrivateRoute,ProvideAuth} from "./other/auth"
4 | import "./App.css"
5 |
6 | import NavBar from "./components/NavBar"
7 | import MainMenu from "./components/MainMenu/MainMenu"
8 | import AccountInfo from "./components/AccountInfo"
9 | import Login from "./components/Login"
10 |
11 | import LateralBar from "./components/LateralBar/LateralBar"
12 |
13 | import MainLive from "./components/Live/MainLive"
14 | import Groups from "./components/Group/Groups"
15 | import Search from "./components/Search/Search"
16 | import EpgFullListing from "./components/Epg-Fullscreen/EpgFullListing"
17 |
18 | import MainVod from "./components/Vod/MainVod"
19 |
20 | import {useDispatch} from "react-redux"
21 | import {setTimer60} from "./actions/timer60"
22 | import {setTimer5} from "./actions/timer5"
23 |
24 |
25 | function App() {
26 | const dispatch = useDispatch()
27 | setInterval(() => dispatch(setTimer60()), 50000);
28 | setInterval(() => dispatch(setTimer5()), 5000);
29 |
30 | if(window.location.protocol !== 'https:' && window.https===true)
31 | window.location = window.location.href.replace("http","https");
32 | else if(window.location.protocol === 'https:' && window.https===false)
33 | window.location = window.location.href.replace("https","http");
34 |
35 | let url = window.location.hash.replace("#","");
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | );
77 | }
78 |
79 | export default App;
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/components/Live/BLUE/ChannelBLUE.js_OLD:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import ChannelEpg from "../ChannelEpg"
3 | import {useRef} from 'react'
4 |
5 | const Li = styled.li`
6 | /*border-radius: .4rem;*/
7 | vertical-align: middle;
8 | display: inline-flex;
9 | justify-content: center;
10 | align-items: center;
11 |
12 | &:focus{
13 | outline-width: 0px;
14 | background-color: #203d79;
15 | transition: all .3s ease-in-out;
16 | cursor: pointer;
17 | }
18 |
19 | &:hover{
20 | background-color: #203d79;
21 | transition: all .3s ease-in-out;
22 | cursor: pointer;
23 | }
24 |
25 | & + & {
26 | margin-top: 10px;
27 | border-top: #203d79 1px solid;
28 | padding-top: 10px;
29 | padding-bottom: 10px;
30 | }
31 |
32 | `
33 |
34 | const ChannelNumber = styled.div`
35 | align-self: center;
36 | text-align: left;
37 | padding: 0;
38 | padding-left: 1rem;
39 |
40 | & > div{
41 | font-size: 1rem !important;
42 | }
43 | `
44 | const HeaderChannel = styled.div`
45 | padding-bottom: .4rem!important;
46 | line-height: 1!important;
47 | font-size: 1rem;
48 | font-weight: 700;
49 | `
50 | const Logo = styled.div`
51 | background: #203d79;
52 | padding: .1rem .1rem;
53 | border-radius: .2rem;
54 | color: #fff;
55 | text-align: center;
56 | width: 100%;
57 | font-weight: bold;
58 | min-height: 2.4rem;
59 | max-height: 2.4rem;
60 |
61 | &>img{
62 | max-height: 2.2rem;
63 | height: auto;
64 | width: auto;
65 | padding-left: 0.5rem;
66 | padding-right: 0.5rem;
67 | }
68 | `
69 |
70 | const ChannelContainer = styled.div`
71 | /*padding-bottom: .2rem;
72 | padding-top: .2rem;*/
73 | `
74 |
75 | const Channel = ({ Name, Image, Epg, Number, id, selected, selectEvent, fullScreenEvent, keydown, virtualScrollStyle }) => {
76 | const inputEl = useRef(null);
77 |
78 | const style = {}
79 | if(selected){
80 | style.backgroundColor = "#203d79";
81 | style.transition = "all .3s ease-in-out";
82 | style.cursor = "pointer";
83 | style.color = "#e9d454!important"
84 | }
85 |
86 |
87 |
88 | return (
89 |
90 |
91 |
92 |
93 |
94 | {Number}
95 |
96 |
97 |
98 |
99 |
{this.onerror=null;this.src='../images/nologo.png?1'}} */ align="middle" alt="" />
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | )
108 | }
109 |
110 | export default Channel
111 |
--------------------------------------------------------------------------------
/src/components/MainMenu/MainMenu.js:
--------------------------------------------------------------------------------
1 | import React, {useState,useEffect} from 'react'
2 | import styled from "styled-components"
3 | import {useHistory} from 'react-router-dom'
4 |
5 | import "./MainMenu.css"
6 | const Container = styled.div`
7 | background-color: #212529;
8 | position:absolute;
9 | width:100%;
10 | height:100%;
11 | top:0;
12 | left:0;
13 | `
14 |
15 | const Box = styled.div`
16 | position: fixed;
17 | z-index: 98;
18 | &:before, &:after {
19 | content: "";
20 | position: fixed;
21 | width: 100vw;
22 | height: 100vh;
23 | background: rgba(20, 21, 26,0.6);
24 | border-bottom-left-radius: 200%;
25 | z-index: -1;
26 | -webkit-transition: -webkit-transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
27 | transition: -webkit-transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
28 | transition: transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
29 | transition: transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, -webkit-transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
30 | -webkit-transform: translateX(100%) translateY(-100%);
31 | transform: translateX(100%) translateY(-100%);
32 | }
33 | &:after {
34 | background: rgba(9,9,12,1);
35 | -webkit-transition-delay: 0s;
36 | transition-delay: 0s;
37 | }
38 | &:before {
39 | -webkit-transition-delay: .2s;
40 | transition-delay: .2s;
41 | }
42 | `
43 |
44 | const MainMenu = () => {
45 | const [style, setStyle] = useState();
46 | const history = useHistory();
47 |
48 | useEffect(() => {
49 | setTimeout(()=>{
50 | setStyle("nav-active");
51 | document.querySelector(".nav__list-item") && (document.querySelector(".nav__list-item").focus());
52 | },10)
53 | },[]);
54 |
55 | const redirect = (page) =>{
56 | setStyle();
57 | setTimeout(()=>{
58 | history.push(page)
59 | },600)
60 | }
61 |
62 | const remoteController = (event) =>{
63 | const active = document.activeElement;
64 | if (event.keyCode === 40 && active.nextSibling) {
65 | active.nextSibling.focus();
66 | } else if (event.keyCode === 38 && active.previousSibling) {
67 | active.previousSibling.focus();
68 | }
69 | //clearTimeout(timeout);
70 | }
71 |
72 | /*const stopRemote = () =>{
73 | clearTimeout(timeout);
74 | }*/
75 |
76 | const moveFocus = (event) => {
77 | remoteController(event);
78 | }
79 |
80 | return (
81 |
82 |
83 |
84 |
85 | - redirect("/live/category/")}>
86 | - redirect("/movie/")}>
87 | - redirect("/series/")}>
88 | - redirect("/info/")}>
89 |
90 |
91 |
92 |
93 | )
94 | }
95 |
96 | export default MainMenu
97 |
--------------------------------------------------------------------------------
/src/components/Live/BLUE/ChannelsBLUE.js_OLD:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import {useSelector, useDispatch} from "react-redux"
3 | import Channel from "../Channel"
4 | import {setPlaylingChannel} from "../../../actions/playingChannel"
5 | import {setFullScreen} from "../../../actions/fullScreen"
6 | import {setVolume} from "../../../actions/volume"
7 | import { FixedSizeList as List } from "react-window";
8 | import {convertRemToPixels,convertVhToPixels} from "../../../other/convert-rem"
9 | import {useEffect} from 'react'
10 |
11 | const ChannelsList = styled.ul`
12 | height: calc(95vh - 3rem);
13 | padding: .8rem .6rem;
14 | background-color: #162f65;
15 | border-radius: 5px;
16 | list-style-type: none;
17 |
18 | &:focus{
19 | outline-width: 0px;
20 | }
21 | `
22 |
23 | const Channels = () => {
24 | const channelsPlaylist = useSelector(state => state.playlist);
25 | const isFullScreen = useSelector(state => state.fullScreen);
26 | const playingChannel = useSelector(state => state.playingCh);
27 |
28 | const dispatch = useDispatch()
29 |
30 | let timeout;
31 | const stopRemote = () =>{
32 | clearTimeout(timeout);
33 | }
34 |
35 | const moveFocus = (event) => {
36 | remoteController(event);
37 | timeout = setTimeout(moveFocus, 500, event);
38 | }
39 |
40 | const remoteController = (event) => {
41 | const active = document.activeElement;
42 | if (event.keyCode === 40 && active.nextSibling) {
43 | active.nextSibling.focus();
44 | } else if (event.keyCode === 38 && active.previousSibling) {
45 | active.previousSibling.focus();
46 | } else if (event.keyCode === 107 || event.keyCode === 109) {
47 | dispatch(setVolume(event.keyCode === 107))
48 | } else if(event.keyCode === 27 || event.keyCode === 8){ //BACK
49 |
50 | } else if(event.keyCode === 39){ //RIGHT
51 |
52 | }
53 | clearTimeout(timeout);
54 | }
55 |
56 | useEffect(() => {
57 | document.getElementById("selectedCh") && (document.getElementById("selectedCh").focus())
58 | }, [isFullScreen]);
59 |
60 | let interval;
61 | const Row = ({ index, style }) => (
62 | {
63 | dispatch(setPlaylingChannel(channelsPlaylist[index]))
64 | interval = setInterval(() => {document.getElementById("selectedCh").focus();clearInterval(interval)}, 200)
65 | }} fullScreenEvent={() => dispatch(setFullScreen(true))} onKeyDown={moveFocus} onKeyUp={stopRemote} virtualScrollStyle={style} selected={playingChannel.stream_id === channelsPlaylist[index].stream_id} />
66 | );
67 |
68 | return (
69 |
70 |
77 | {Row}
78 |
79 |
80 |
81 |
82 | )
83 | }
84 |
85 |
86 |
87 | export default Channels
88 |
--------------------------------------------------------------------------------
/src/components/Live/Channel.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect} from "react"
2 | import styled from "styled-components"
3 | import ChannelEpg from "./ChannelEpg"
4 | import "../../other/Transition.css"
5 |
6 | const Li = styled.li`
7 | /*border-radius: .4rem;*/
8 | vertical-align: middle;
9 | display: inline-flex;
10 | justify-content: center;
11 | align-items: center;
12 | color: #829196;
13 | width: 90% !important;
14 | left: 5% !important;
15 | border-bottom: #000 1px solid;
16 |
17 | &:focus, &:hover{
18 | outline-width: 0px;
19 | /*background-color: #fff;
20 | transition: background-color .3s ease-in-out;
21 | color: #020d18 !important;
22 | border-radius: 5px;*/
23 | cursor: pointer;
24 | border-bottom: #000 0px solid !important;
25 | }
26 |
27 | &:hover *, &:focus *{
28 | /*color: #020d18 !important;*/
29 | cursor :pointer;
30 | }
31 |
32 |
33 | & + & {
34 | margin-top: 10px;
35 | padding-top: 10px;
36 | padding-bottom: 10px;
37 | }
38 |
39 | `
40 |
41 | const ChannelNumber = styled.div`
42 | align-self: center;
43 | text-align: left;
44 | padding: 0;
45 | padding-left: 1rem;
46 |
47 | & > div{
48 | font-size: 1rem !important;
49 | }
50 | `
51 | const HeaderChannel = styled.div`
52 | padding-bottom: .4rem!important;
53 | line-height: 1!important;
54 | font-size: 1rem;
55 | font-weight: 700;
56 | `
57 | const Logo = styled.div`
58 | /*background: #203d79;*/
59 | padding: .1rem .1rem;
60 | border-radius: .2rem;
61 | text-align: center;
62 | width: 100%;
63 | font-weight: bold;
64 | min-height: 3rem;
65 | max-height: 3rem;
66 |
67 | background-position-x: center;
68 | background-position-y: center;
69 | background-size: contain;
70 | background-repeat: no-repeat;
71 |
72 | &>img{
73 | max-height: 3rem;
74 | height: auto;
75 | width: auto;
76 | padding-left: 0.5rem;
77 | padding-right: 0.5rem;
78 | }
79 | `
80 |
81 | const ChannelContainer = styled.div`
82 | /*padding-bottom: .2rem;
83 | padding-top: .2rem;*/
84 | `
85 |
86 | const Channel = ({ Name, Image, Epg, Shift, Number, id, selected, selectEvent, virtualScrollStyle }) => {
87 | const style = {}
88 |
89 | useEffect(()=>{
90 | if(selected){
91 | setTimeout(() => {
92 | document.getElementById("selectedCh") && (document.getElementById("selectedCh").className += " hvr-shutter-in-horizontal-on")
93 | }, 100)
94 | }
95 | },[])
96 |
97 |
98 |
99 | return (
100 |
101 |
102 |
103 |
104 |
105 | {Number}
106 |
107 |
108 |
109 |
110 | {//
{this.onerror=null;this.src='../images/nologo.png?1'}} */ align="middle" alt="" />
111 | }
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | )
120 | }
121 |
122 | export default Channel
123 |
--------------------------------------------------------------------------------
/public/img/placeholder-channel-events_border.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/Programme-fake.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import {useSelector} from "react-redux"
4 |
5 | const dateFormat = require("dateformat");
6 |
7 | const Container = styled.li`
8 | position: absolute;
9 | left: 0;
10 | height: 100%;
11 | background-color: #fff;
12 | transition: all 0.3s ease 0s;
13 |
14 | &:hover, &:focus{
15 | z-index: 2;
16 | width: fit-content !important;
17 | max-width: 600px;
18 | text-overflow: ellipsis;
19 | white-space: nowrap;
20 | overflow: hidden;
21 | outline-width: 0px;
22 | }
23 |
24 | &:focus{
25 | border: 1px solid #0071ff;
26 | box-shadow: 0 2px 4px 0 rgb(0 0 0 / 6%);
27 | outline-width: 0px;
28 | border-radius: .25rem;
29 | }
30 |
31 | & > div{
32 | text-align: initial;
33 | position: relative;
34 | color: #303030;
35 | background-color: #f0f0f0;
36 | margin-right: 2px;
37 | margin-left: 2px;
38 | height: 100%;
39 | border-radius: .25rem;
40 | transition: all 0.3s ease 0s;
41 |
42 |
43 | &:hover, &:focus{
44 | cursor: pointer;
45 | border: 1px solid #0071ff;
46 | box-shadow: 0 2px 4px 0 rgb(0 0 0 / 6%);
47 | outline-width: 0px;
48 | }
49 | }
50 |
51 | & > div > div{
52 | display: flex;
53 | flex-direction: row;
54 | flex-wrap: nowrap;
55 | height: 100%;
56 | }
57 | `
58 | const BodyData = styled.div`
59 | padding: 1rem 1.5rem;
60 | height: 100%;
61 |
62 | text-overflow: ellipsis;
63 | white-space: nowrap;
64 | overflow: hidden;
65 | `
66 |
67 | const Title = styled.div`
68 | margin: 0;
69 | font-size: 1rem;
70 | font-weight: 500;
71 | line-height: 1.125rem;
72 |
73 | text-overflow: ellipsis;
74 | white-space: nowrap;
75 | overflow: hidden;
76 | `
77 |
78 | const Description = styled.div`
79 | padding-top: .25rem;
80 | font-size: .875rem;
81 | max-width: 500px;
82 | flex-wrap: nowrap!important;
83 |
84 | text-overflow: ellipsis;
85 | white-space: nowrap;
86 | overflow: hidden;
87 | `
88 |
89 | const Time = styled.div`
90 | padding-top: .75rem;
91 | font-size: .875rem;
92 |
93 | text-overflow: ellipsis;
94 | white-space: nowrap;
95 | overflow: hidden;
96 | `
97 | const ProgressBar = styled.div`
98 | position: absolute;
99 | top: 0;
100 | bottom: 0;
101 | left: 0;
102 | right: 0;
103 | `
104 |
105 | const Progress = styled.div`
106 | background: repeating-linear-gradient(45deg,rgba(0,0,0,.5),rgba(0,0,0,.5) 1px,#b4c6df 1px,#b4c6df 7px);
107 | opacity: .2;
108 | height: 100%;
109 | `
110 |
111 |
112 | const ProgrammeFake = ({start, stop, dayTime, chId}) => {
113 | const h24Format = useSelector(state => state.h24);
114 | /*
115 | length = each minute is 0.25px;
116 | start = each minute is 2.73px
117 | */
118 | const lengthStyle = ((stop-start)/3600000)*17;
119 | const startStyle = -2+((start-dayTime)/3600000)*17;
120 | const Spacing = {
121 | minWidth : lengthStyle + "rem",
122 | width : lengthStyle + "rem",
123 | transform: `translateX(${startStyle}rem)`
124 | }
125 |
126 |
127 | return (
128 |
129 |
130 |
131 |
132 | No programme available
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | )
143 | }
144 |
145 | export default ProgrammeFake
146 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/TimeBar/TimeLine.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import {convertRemToPixels} from "../../../other/convert-rem"
4 |
5 | const Container = styled.div`
6 | display: flex;
7 | flex-flow: row;
8 | flex-wrap: nowrap!important;
9 | transform: translateY(0px);
10 | `
11 |
12 | const ArrowContainer = styled.div`
13 | position: sticky;
14 | top: 0;
15 | z-index: 3;
16 | display: inline-block;
17 |
18 | & > div{
19 | justify-content: flex-end;
20 | background: linear-gradient(to right,#fff 0,#fff 92%,rgba(255,255,255,0) 100%);
21 | display: flex;
22 | align-items: center;
23 | height: 4.5rem;
24 | }
25 |
26 | `
27 | const Arrow = styled.button`
28 | border: none!important;
29 | cursor: pointer;
30 | min-width: 0!important;
31 | background-color: #fff;
32 | box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
33 | border-radius: 1.375rem;
34 | width: 2.5rem;
35 | height: 2.5rem;
36 | user-select: none;
37 | -webkit-tap-highlight-color: transparent;
38 | -webkit-appearance: none;
39 |
40 | &:focus{
41 | outline-width: 0px;
42 | }
43 | `
44 |
45 | const TimeLineDiv = styled.div`
46 | width: 6760px;
47 | display: inline-block;
48 | background: #fff;
49 | box-shadow: 0 4px 8px 0 rgb(0 0 0 / 10%);
50 |
51 | & > div{
52 | height: 4.5rem;
53 | display: flex;
54 | align-items: center;
55 | position: relative;
56 | }
57 |
58 | & > div > button{
59 | position: absolute;
60 | left: 0;
61 | width: 1rem;
62 | white-space: nowrap;
63 | background: 0 0;
64 | border: none;
65 | outline: 0;
66 | padding: .4375rem 1.4375rem;
67 | font-size: .875rem;
68 | -webkit-tap-highlight-color: transparent;
69 | user-select: none;
70 | text-transform: none;
71 | }
72 |
73 | `
74 |
75 | const TimeLine = ({h24,scrollBtn}) => {
76 | let hours = [];
77 | if(h24 === true){
78 | for(let i = 0; i < 23; i++){
79 | let h= i+1<10 ? "0"+(i+1) : i+1;
80 | hours.push({text:`${h}:00`,margin: 12 + (17*i)});
81 | }
82 | }else{
83 | let counter = 0;
84 | for(let i = 0; i < 12; i++){
85 | let h = i;
86 | if(h===0)
87 | h=12;
88 | else h = h<10 ? "0"+i : i;
89 | hours.push({text:`${h}:00 AM`,margin: 12 + (17*counter)});
90 | counter++;
91 | }
92 | for(let i = 0; i < 12; i++){
93 | let h = i;
94 | if(h===0)
95 | h=12;
96 | else h = h<10 ? "0"+i : i;
97 | hours.push({text:`${h}:00 PM`,margin: 12 + (17*counter)});
98 | counter++;
99 | }
100 | }
101 |
102 |
103 | return (
104 |
105 | scrollBtn(-convertRemToPixels(30))}>
106 |
109 |
110 |
111 |
112 | {hours.map((x,id)=>(
113 |
116 | ))}
117 |
118 |
119 | scrollBtn(convertRemToPixels(30))}>
120 |
123 |
124 |
125 | )
126 | }
127 |
128 | export default TimeLine
129 |
--------------------------------------------------------------------------------
/src/other/auth.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext, createContext } from "react";
2 | import {Route,Redirect} from "react-router-dom"
3 | import * as axios from "./axios";
4 | import {setInfo} from "./user_info"
5 | import Cookies from 'js-cookie'
6 | import {initDb} from "./local-db"
7 |
8 | const authContext = createContext();
9 |
10 | export function ProvideAuth({ children }) {
11 | const auth = useProvideAuth();
12 | return {children};
13 | }
14 |
15 | export const useAuth = () => {
16 | return useContext(authContext);
17 | };
18 |
19 | export function useProvideAuth() {
20 | const [auth, setAuth] = useState(0);
21 |
22 | const signin = (dns, username, password, successFallback, failFallback) => {
23 | if(dns)
24 | axios.setDns(dns);
25 |
26 | axios.post("player_api.php",{
27 | username: username.trim(),
28 | password: password.trim(),
29 | }).then(result => {
30 | if(result){
31 | if(result.data)
32 | result = result.data
33 | else if(result.response && result.response.data)
34 | result = result.response.data
35 | }
36 | if(result && result.user_info){
37 | if(result.iptveditor)
38 | axios.setDns(`${process.env.REACT_APP_IPTVEDITOR_API}webplayer`);
39 | if(result.user_info.auth === 0)
40 | failFallback("No account found","No account found with inserted credentials." + (window.location.host.includes("iptveditor.com") ? "
To login use your IPTVEditor's playlist username and password, not your email." : ""));
41 | else if(result.user_info.auth){
42 | if(result.user_info.status !== "Active")
43 | failFallback("Account expired",`Account expired on ${new Date(parseInt(result.user_info.exp_date+"000")).toGMTString()}`);
44 | else {
45 | setAuth(1);
46 | setInfo(result.user_info, result.server_info);
47 | initDb();
48 | successFallback && (successFallback());
49 |
50 | }
51 | }
52 | }else if(result.title){
53 | failFallback && (failFallback(result.title,result.body));
54 | }else{
55 | failFallback && (failFallback("Server error","Server didn't generated any reply. Please try later #2"));
56 | }
57 | }).catch(err => {
58 | console.log(err);
59 | if(err && err.response && err.response.data && err.response.data.user_info && err.response.data.user_info.auth === 0)
60 | failFallback && (failFallback("No account found","No account found with inserted credentials." + (window.location.host.includes("iptveditor.com") ? "
To login use your IPTVEditor's playlist username and password, not your email." : "")));
61 | else failFallback && (failFallback("Server error","Server didn't generated any reply. Please try later #1"));
62 | })
63 | };
64 |
65 | const authLogin = (fallback) =>{
66 |
67 | const dns = window.dns.length === 0 && (Cookies.get("dns")) ;
68 | const username = Cookies.get("username");
69 | const password = Cookies.get("password");
70 |
71 | if(username && password)
72 | signin(dns,username,password,fallback);
73 | }
74 |
75 | const signout = (action) => {
76 | setAuth(null);
77 | action && (action());
78 | };
79 |
80 | const isAuth = () => {
81 | return !!auth;
82 | }
83 |
84 | return {
85 | signin,
86 | signout,
87 | isAuth,
88 | authLogin
89 | };
90 | }
91 |
92 |
93 | export function PrivateRoute({ children, ...rest }) {
94 | let auth = useAuth();
95 | return (
96 |
99 | auth.isAuth() ? (
100 | children
101 | ) : (
102 |
108 | )
109 | }
110 | />
111 | );
112 | }
--------------------------------------------------------------------------------
/src/components/Series/Episode.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | min-height: 2rem;
6 | height: 6.5rem;
7 | padding: 0.6rem;
8 | border-radius: .2rem;
9 | border: 0.1rem solid transparent;
10 |
11 | border-top-color: black;
12 | border-down-color: black;
13 |
14 | cursor:pointer;
15 |
16 | transition: background-color 0.3s ease, border-color 0.3s ease;
17 |
18 | &:hover{
19 | background-color: #333;
20 | border-color: white;
21 | }
22 |
23 | & .playbtn{
24 | opacity: 0;
25 | }
26 |
27 | &:hover .playbtn{
28 | opacity: 1;
29 | }
30 |
31 | & *{
32 | cursor:pointer;
33 | }
34 | `
35 |
36 | const Number = styled.div`
37 | text-align:right;
38 | `
39 |
40 | const Image = styled.img`
41 | max-width: 100%;
42 | display: block;
43 | `
44 |
45 | const PlayButton = styled.div`
46 | position: absolute;
47 | display: flex;
48 | place-content: center;
49 | align-items: center;
50 | height: 100%;
51 | width: calc(100% - 1.5rem);
52 | background-color: #00000033;
53 | transition: opacity 0.3s ease;
54 |
55 | `
56 |
57 | const TitleContainer = styled.div`
58 | display: inline;
59 | align-items: center;
60 |
61 | & > * {
62 | width: 100%;
63 | height: 100%;
64 | overflow: hidden;
65 | text-overflow: ellipsis;
66 | display: -webkit-box;
67 | -webkit-box-orient: vertical;
68 | }
69 |
70 | & > h5{
71 | -webkit-line-clamp: 1;
72 | }
73 |
74 | & > label{
75 | -webkit-line-clamp: 2;
76 | font-weight: 300;
77 | }
78 | `
79 |
80 | const Bar = styled.div`
81 | position: absolute;
82 | display: flex;
83 | height: 4%;
84 | bottom: 0;
85 | width: calc(100% - 1.9rem);
86 | background-color: var(--first-color);
87 |
88 | & > div{
89 | height:100%;
90 | background-color : var(--second-color);
91 | }
92 | `
93 |
94 | const DownloadIcon = styled.a`
95 | transition: font-size 0.2s ease;
96 | font-size: 1rem;
97 | cursor: pointer;
98 | color: white;
99 | &:hover{
100 | font-size: 1.4rem;
101 | color:white;
102 | }
103 | `
104 |
105 | const Episode = ({episode, image, duration, title, description, selected, playEpisode, percentage, url}) => {
106 | return (
107 | playEpisode(episode)}>
108 |
109 | {episode}
110 |
111 |
112 |
113 | {selected && percentage > 3 && percentage < 95 && (
114 |
115 |
116 |
117 | )}
118 |
119 |
120 |
121 |
122 |
123 | {title}
124 |
125 | {description && (
126 |
129 | )}
130 |
131 |
132 | {secondsToHms(duration)}
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | )
141 | }
142 |
143 | export default Episode
144 |
145 | const secondsToHms = d => {
146 | if(!d)
147 | return "";
148 | d = parseInt(d);
149 | const h = Math.floor(d / 3600);
150 | const m = Math.floor((d % 3600) / 60);
151 | const s = Math.floor((d % 3600) % 60);
152 |
153 | if (h) {
154 | return `${h}:${m<10 ? "0"+m : m}:${s<10 ? "0"+s : s}`;
155 | }
156 |
157 | return `${m}:${s<10 ? "0"+s : s}`;
158 | };
159 |
--------------------------------------------------------------------------------
/src/components/Search/Search.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from "react"
2 | import styled from 'styled-components'
3 | import { useHistory, useParams, useLocation } from 'react-router';
4 | import "./Search.css"
5 |
6 | const Container = styled.div`
7 | position:absolute;
8 | width:100%;
9 | height:100%;
10 | top:0;
11 | left:0;
12 | z-index: 5;
13 | transition: all .5s ease;
14 | opacity: 0;
15 | `
16 |
17 | const Box = styled.form`
18 | position: relative;
19 | width: 50% ;
20 | left: 25% ;
21 | top: 50% ;
22 | display:flex;
23 | `
24 |
25 | const Input = styled.input`
26 | &:focus{
27 | color:white;
28 | border-color: var(--second-color);
29 | box-shadow: 0 0 0 0.2rem var(--second-color-shadow);
30 | background-color: var(--first-color);
31 | transition: all .5s ease;
32 | }`
33 |
34 | const ClearButton = styled.button`
35 | color: #fff;
36 | background-color: var(--first-color);
37 | border-color: var(--second-color);
38 | transition: all .5s ease;
39 | margin-right: 0.3rem;
40 |
41 |
42 | &:hover, &:focus{
43 | background-color: #829196;
44 | color:white;
45 | border-color: #829196;
46 | box-shadow: 0 0 0 0.2rem #82919608;
47 | transition: all .5s ease;
48 | }
49 | `
50 |
51 | const SearchButton = styled.button`
52 | color: #fff;
53 | background-color: var(--second-color);
54 | transition: all .5s ease;
55 | margin-left: 0.3rem;
56 |
57 |
58 | &:hover, &:focus{
59 | background-color: #829196;
60 | color:white;
61 | border-color: #829196;
62 | box-shadow: 0 0 0 0.2rem #82919608;
63 | transition: all .5s ease;
64 | }
65 | `
66 |
67 | const Search = () => {
68 | const query = useQuery();
69 | const history = useHistory();
70 |
71 | const [searchValue, setSearchValue] = useState(query.get("search") || "");
72 | const [startingEffect, setStartingEffect] = useState();
73 |
74 | const { playingMode, category } = useParams();
75 |
76 | useEffect(() => {
77 | setTimeout(()=> setStartingEffect("blur-search"),1)
78 | }, [])
79 |
80 | const handleSubmit = (evt) => {
81 | evt.preventDefault();
82 | alert(`Submitting Name ${searchValue}`)
83 | }
84 |
85 |
86 |
87 | const close = (e) => {
88 | e.preventDefault();
89 | if(!searchValue){
90 | clear();
91 | return;
92 | }
93 | setStartingEffect();
94 | setTimeout(()=> {
95 | history.replace(category ? `/${playingMode}/category/${category}/?search=${searchValue}` : `/${playingMode}/?search=${searchValue}`);
96 | },300)
97 | }
98 |
99 | const clear = () => {
100 | setStartingEffect();
101 | setTimeout(()=> {
102 | history.replace(category ? `/${playingMode}/category/${category}` : `/${playingMode}/`);
103 | },300)
104 | }
105 |
106 | return (
107 | <>
108 |
109 |
110 |
111 |
112 |
113 | < Input className = "form-control"
114 | type = "text"
115 | spellCheck = {false}
116 | placeholder = "Search stream..."
117 | onChange = {(e) => setSearchValue(e.target.value)}
118 | value = {searchValue}
119 | />
120 |
121 |
122 |
123 |
124 |
125 | {/*showKeyboard !== false && isMag && ()*/}
126 | >
127 | )
128 | }
129 |
130 | const useQuery = () => {
131 | return new URLSearchParams(useLocation().search);
132 | }
133 |
134 | export default Search
135 |
--------------------------------------------------------------------------------
/src/components/Live/Channels.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect,useRef, useState} from 'react'
2 | import styled from "styled-components"
3 | import {useSelector, useDispatch} from "react-redux"
4 | import Channel from "./Channel"
5 | import {setPlayingChannel} from "../../actions/playingChannel"
6 | import { FixedSizeList as List } from "react-window";
7 | import {convertRemToPixels,convertVhToPixels} from "../../other/convert-rem"
8 | import {useHistory,useLocation} from "react-router-dom";
9 | import Popup from "../Popup/Popup"
10 |
11 | const ChannelsList = styled.ul`
12 | height: calc(95vh - 3rem);
13 | padding: .8rem .6rem;
14 | background-color: var(--first-color);
15 | list-style-type: none;
16 | border-left: 1px solid #000;
17 | transform: translateY(-1px);
18 | box-shadow: 0 7px 14px rgb(50 50 93 / 10%), 0 3px 6px rgb(0 0 0 / 8%);
19 | &:focus{
20 | outline-width: 0px;
21 | }
22 | `
23 |
24 | const Channels = ({playlist}) => {
25 | const playingChannel = useSelector(state => state.playingCh);
26 |
27 | const query = useQuery();
28 | const searchText = query.get("search");
29 |
30 | const [showPopup, setShowPopup] = useState(false);
31 | const [channelsPlaylist, setChannelsPlaylist] = useState(playlist);
32 |
33 |
34 | const dispatch = useDispatch()
35 | const history = useHistory();
36 | const location = useLocation();
37 | const listRef = useRef();
38 |
39 |
40 | useEffect(() => {
41 | if(playingChannel){
42 | setTimeout(()=>focusChannel(),100)
43 | }
44 | }, [playingChannel]);
45 |
46 | useEffect(() => {
47 | setChannelsPlaylist(playlist)
48 | setTimeout(()=>focusChannel(),100)
49 | }, [playlist]);
50 |
51 |
52 | useEffect(() => {
53 | const searched = searchText ? playlist.filter(x=> x.name.toLowerCase().includes(searchText.toLowerCase())) : playlist
54 | if(searched.length>0){
55 | setChannelsPlaylist(searched)
56 | setTimeout(() => focusChannel(), 100);
57 | }else if(searchText)
58 | setShowPopup(1)
59 | else history.push("/live/category/")
60 | }, [searchText]);
61 |
62 | const focusChannel = () => {
63 | if(document.getElementById("selectedCh"))
64 | document.getElementById("selectedCh").focus()
65 | else if(document.querySelector(".channel"))
66 | document.querySelector(".channel").focus()
67 | }
68 |
69 |
70 | const Row = ({ index, style }) => (
71 | {
78 | dispatch(setPlayingChannel(channelsPlaylist[index]))
79 | }}
80 | virtualScrollStyle={style}
81 | selected={playingChannel && playingChannel.stream_id === channelsPlaylist[index].stream_id} />
82 | );
83 |
84 | return (
85 | <>
86 |
87 |
95 | {Row}
96 |
97 |
98 | {showPopup && {
99 | setShowPopup(0);
100 | focusChannel();
101 | history.replace(location.pathname.split("?")[0])
102 | }}/>}
103 | >
104 | )
105 | }
106 |
107 | const useQuery = () => {
108 | return new URLSearchParams(useLocation().search);
109 | }
110 |
111 | export default Channels
112 |
--------------------------------------------------------------------------------
/src/components/Live/MainLive.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState} from 'react'
2 | import Player from "../Player/Player"
3 | import EpgListing from "./EpgListing"
4 | import Channels from "./Channels"
5 |
6 | import styled from "styled-components"
7 | import {useSelector, useDispatch} from "react-redux"
8 |
9 | import {setPlaylist} from "../../actions/set-Playlist"
10 | import {setGroupList} from "../../actions/set-Group"
11 |
12 | import {resetMemory, setGroup} from "../../other/last-opened-mode"
13 |
14 | import {loadGroup,loadPlaylist} from "../../other/load-playlist"
15 | import {useParams, useHistory} from "react-router-dom";
16 |
17 | import Popup from "../Popup/Popup"
18 |
19 | import DB from "../../other/local-db"
20 |
21 | const Main = styled.div`
22 | padding: 3vh 1vw;
23 | background-color: var(--first-color);
24 | height: calc(100vh - 3rem);
25 | z-index:1;
26 | transition: filter 0.5s ease;
27 |
28 | `
29 |
30 | const ChannelTitle = styled.h5`
31 | color:white;
32 | white-space: nowrap;
33 | text-overflow: ellipsis;
34 | `
35 |
36 |
37 | const MainLive = () => {
38 | const [blurBackground, setBlurBackground] = useState()
39 |
40 | const playingChannel = useSelector(state => state.playingCh);
41 | const playlist = useSelector(state => state.playlist);
42 | const dispatch = useDispatch()
43 | const history = useHistory()
44 |
45 | const { category } = useParams();
46 | const [showPopup, setShowPopup] = useState(false);
47 |
48 | useEffect(()=>{
49 | const fun = async() => {
50 | setBlurBackground(((isNaN(category) || category === undefined) || history.location.pathname.includes("menu")) ? {filter:"blur(.5rem)", pointerEvents: "none"} : {});
51 |
52 | if(category != undefined && category != 0){
53 | let chs = await loadPlaylist("live",category)
54 | chs = chs || [];
55 | if(category==="fav")
56 | chs = chs.filter(x=> DB.findOne("live",x.stream_id,true))
57 | dispatch(setPlaylist(chs));
58 | if(chs.length === 0)
59 | setShowPopup(1)
60 | }else if(resetMemory("live")){
61 | await loadGroup("live").then(gps => {
62 | if(!gps || gps.length===0){
63 | history.replace("/")
64 | return;
65 | }
66 | gps.unshift({category_name : "Only favorites", category_id:"fav"})
67 | setGroup(gps[1].category_id)
68 | dispatch(setGroupList(gps));
69 | history.replace("/live/category/"+gps[1].category_id+"/")
70 | })
71 | }else history.replace("/live/category/");
72 | }
73 | fun()
74 | },[dispatch,category])
75 |
76 | useEffect(() => {
77 | if(history.location)
78 | setBlurBackground(((isNaN(category) && category === undefined) || history.location.pathname.includes("menu")) ? {filter:"blur(.5rem)", pointerEvents: "none"} : {});
79 | }, [history.location.pathname, category])
80 |
81 |
82 | return (
83 |
84 |
85 |
86 |
87 |
{playingChannel ? playingChannel.name : "No channel selected"}
88 | {
}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | {showPopup &&
{
97 | setBlurBackground({})
98 | setShowPopup(0);
99 | history.replace("/live/category/")
100 | }}/>}
101 | {/*window.gSTB && ()*/}
102 |
103 | )
104 | }
105 |
106 | export default MainLive
107 |
--------------------------------------------------------------------------------
/public/proxy.php:
--------------------------------------------------------------------------------
1 | 0) {
64 | // Limit POST data size to prevent memory exhaustion
65 | $max_post_size = 1024 * 100; // 100KB
66 | $post_json = json_encode($_POST);
67 |
68 | if (strlen($post_json) > $max_post_size) {
69 | http_response_code(413);
70 | die('Request too large');
71 | }
72 |
73 | $post_data = $post_json;
74 | }
75 |
76 | // Set timeout and configure stream context with security options
77 | $timeout = 30; // 30 seconds timeout
78 |
79 | try {
80 | if ($post_data !== null) {
81 | $options = array(
82 | 'http' => array(
83 | 'method' => 'POST',
84 | 'content' => $post_data,
85 | 'header' => "Content-Type: application/json\r\n" .
86 | "Accept: application/json\r\n" .
87 | "User-Agent: StreamityProxy/1.0\r\n",
88 | 'timeout' => $timeout,
89 | 'ignore_errors' => false,
90 | 'follow_location' => 0 // Prevent redirect attacks
91 | )
92 | );
93 | $context = stream_context_create($options);
94 | $result = @file_get_contents($url, false, $context);
95 | } else {
96 | $options = array(
97 | 'http' => array(
98 | 'method' => 'GET',
99 | 'timeout' => $timeout,
100 | 'ignore_errors' => false,
101 | 'follow_location' => 0, // Prevent redirect attacks
102 | 'header' => "User-Agent: StreamityProxy/1.0\r\n"
103 | )
104 | );
105 | $context = stream_context_create($options);
106 | $result = @file_get_contents($url, false, $context);
107 | }
108 |
109 | if ($result === false) {
110 | http_response_code(502);
111 | die('Failed to fetch content');
112 | }
113 |
114 | // Set appropriate content type if available
115 | if (isset($http_response_header)) {
116 | foreach ($http_response_header as $header) {
117 | if (stripos($header, 'Content-Type:') === 0) {
118 | header($header);
119 | break;
120 | }
121 | }
122 | }
123 |
124 | echo $result;
125 |
126 | } catch (Exception $e) {
127 | http_response_code(500);
128 | die('Internal server error');
129 | }
130 |
131 | ?>
--------------------------------------------------------------------------------
/src/components/Live/ChannelEpg.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import styled from "styled-components"
3 | import {useSelector} from "react-redux"
4 | import ChannelEpgBar from "./ChannelEpgBar"
5 | import DB from "../../other/local-db"
6 |
7 | import {getSingleEpgNow, downloadEpgData} from "../../other/epg-database"
8 |
9 | const dateFormat = require("dateformat");
10 |
11 | const HeaderChannel = styled.div`
12 | padding-bottom: .4rem!important;
13 | line-height: 1!important;
14 | font-size: 0.65rem;
15 | font-weight: 700;
16 | `
17 |
18 | const BodyChannel = styled.div`
19 | text-transform: capitalize;
20 | overflow: hidden;
21 | white-space: nowrap;
22 | text-overflow: ellipsis;
23 | `
24 | const Title = styled.label`
25 | font-size: 1rem;
26 | max-width: 100%;
27 | white-space: nowrap;
28 | overflow: hidden;
29 | text-overflow: ellipsis;`
30 |
31 | const TitleEpg = styled.label`
32 | padding-left: 2%;
33 | font-size: 0.9rem;
34 | font-weight: 100;
35 | max-width: 70%;
36 | white-space: nowrap;
37 | overflow: hidden;
38 | text-overflow: ellipsis;`
39 |
40 | /*const PlayingButton = styled.i`
41 | font-size: 2.2rem;
42 | color: #fff;
43 | margin-left:2px;
44 | `*/
45 |
46 | const Favorite = styled.div`
47 | width: 1rem;
48 | display: flex;
49 | align-content: center;
50 | align-self: center;
51 | z-index:10;
52 |
53 | & > i {
54 | margin-left:2px;
55 | }
56 | `
57 |
58 | const ChannelEpg = ({chId, Name, Epg, Shift, isPlaying}) => {
59 | const h24Format = useSelector(state => state.h24);
60 | const timer = useSelector(state => state.timer60);
61 | const [epgNow, setEpgNow] = useState(false);
62 | const [favorite, setFavorite] = useState(null)
63 |
64 | useEffect(() => {
65 | setFavorite(!!DB.findOne("live",chId,true))
66 | if(!Epg)
67 | return;
68 | async function fetchData() {
69 | await downloadEpgData(chId, Epg, 1, Shift);
70 | const newEpg = getSingleEpgNow(Epg,Shift);
71 | if(!newEpg)
72 | setEpgNow(false);
73 | else if(newEpg && (epgNow === false || epgNow.start !== newEpg.start)){
74 | setEpgNow({...newEpg});
75 | }
76 | }
77 | fetchData();
78 | }, []);
79 |
80 | useEffect(() => {
81 | setFavorite(!!DB.findOne("live",chId,true))
82 | }, [isPlaying])
83 |
84 | useEffect(() => {
85 | const newEpg = getSingleEpgNow(Epg,Shift);
86 | if(!newEpg)
87 | setEpgNow(false);
88 | else if(newEpg && (epgNow === false || epgNow.start !== newEpg.start)){
89 | setEpgNow({...newEpg});
90 | }
91 | }, [timer]);
92 |
93 | const setFavoriteGlob = (event) =>{
94 | event.stopPropagation();
95 |
96 | if(!!favorite)
97 | DB.del("live",chId,true)
98 | else DB.set("live",chId,{id: chId}, true)
99 | setFavorite(!favorite);
100 | }
101 |
102 | return (
103 | <>
104 |
105 |
106 | {epgNow !== false && `${dateFormat(new Date(epgNow.start), h24Format)} - ${dateFormat(new Date(epgNow.end), h24Format)}`}
107 |
108 |
109 | {Name}
110 | {epgNow.title}
111 |
112 |
113 | {/*isPlaying ? ()*/}
116 |
117 |
118 |
119 |
120 | {epgNow !== false ?
121 | ()
122 | :
123 |
124 | }
125 |
126 | >
127 | )
128 | }
129 |
130 | export default ChannelEpg
131 |
132 |
--------------------------------------------------------------------------------
/src/components/NavBar.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import {Link,useLocation, useParams} from 'react-router-dom'
3 | import styled from "styled-components"
4 | import {useSelector} from "react-redux"
5 | import {useAuth} from "../other/auth"
6 |
7 |
8 | const Nav = styled.nav`
9 | height: 3rem;
10 | background-color: var(--first-color);
11 | border-bottom: 1px solid #000;
12 | `;
13 |
14 | const ContainerButton = styled.div`
15 | position:absolute;
16 | right:2rem;
17 | top:.5rem;
18 | display: flex;
19 |
20 | & > a{
21 | color: #fff;
22 | align-items: center;
23 | justify-content: center;
24 | font-size: 1rem;
25 | text-transform: uppercase;
26 | display: flex;
27 | background: #829196;
28 | height: 2rem;
29 | width: 2rem;
30 | border-radius: 50%;
31 | }
32 |
33 | `
34 |
35 | const CategoryButton = styled(Link)`
36 | margin-right: 1rem;
37 | &:hover {
38 | width: 11rem;
39 | -webkit-transition: width 1s;
40 | transition: width .5s;
41 | overflow:hidden;
42 | white-space: nowrap;
43 | border-radius:5px;
44 | text-decoration: none;
45 | color: #fff;
46 | padding-left:0.3rem;
47 | padding-right:0.3rem;
48 | }
49 |
50 | &:hover::after {
51 | padding-left:0.3rem;
52 | padding-right:0.3rem;
53 | content: 'Select category';
54 | }
55 | `
56 |
57 | const EPGButton = styled(Link)`
58 | margin-right: 1rem;
59 | &:hover {
60 | width: 8rem;
61 | -webkit-transition: width 1s;
62 | transition: width .5s;
63 | overflow:hidden;
64 | white-space: nowrap;
65 | border-radius:5px;
66 | text-decoration: none;
67 | color: #fff;
68 | padding-left:0.3rem;
69 | padding-right:0.3rem;
70 | }
71 |
72 | &:hover::after {
73 | padding-left:0.3rem;
74 | padding-right:0.3rem;
75 | content: 'TV Guide';
76 | }
77 | `
78 |
79 | const SearchButton = styled(Link)`
80 | margin-right: 1rem;
81 | &:hover {
82 | width: 8rem;
83 | -webkit-transition: width 1s;
84 | transition: width .5s;
85 | overflow:hidden;
86 | white-space: nowrap;
87 | border-radius:5px;
88 | text-decoration: none;
89 | color: #fff;
90 | padding-left:0.3rem;
91 | padding-right:0.3rem;
92 | }
93 |
94 | &:hover::after {
95 | padding-left:0.3rem;
96 | padding-right:0.3rem;
97 | content: 'Search...';
98 | }
99 | `
100 |
101 | const NavButton = styled(Link)`
102 | margin-right: 1rem;
103 | &:hover {
104 | width: 10rem;
105 | -webkit-transition: width 1s;
106 | transition: width .5s;
107 | overflow:hidden;
108 | white-space: nowrap;
109 | border-radius:5px;
110 | text-decoration: none;
111 | color: #fff;
112 | padding-left:0.3rem;
113 | padding-right:0.3rem;
114 | }
115 |
116 | &:hover::after {
117 | padding-left:0.3rem;
118 | padding-right:0.3rem;
119 | content: 'Open menu';
120 | }
121 | `
122 |
123 | const NavBar = () => {
124 | const fullScreen = useSelector(state => state.fullScreen);
125 | const location = useLocation();
126 | let auth = useAuth();
127 |
128 | const { playingMode } = useParams();
129 |
130 | return (
131 |
155 | )
156 | }
157 |
158 | export default NavBar
159 |
--------------------------------------------------------------------------------
/src/components/Series/PlayerSeries.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react'
2 | import ReactNetflixPlayer from "../../other/Player-github/player-github"
3 | import styled from 'styled-components'
4 | import {useParams,useHistory} from "react-router-dom";
5 | import {optimizeName} from "../../other/vod-series-name-optimizer"
6 | import {useSelector} from "react-redux"
7 | import DB from "../../other/local-db"
8 |
9 | const Container = styled.div`
10 | position:absolute;
11 | width: 100%;
12 | height: 100%;
13 | top: 0;
14 | left: 0;
15 | `
16 |
17 | const PlayerSeries = () => {
18 | const {category,season,episode, stream_id} = useParams();
19 | const history = useHistory();
20 |
21 | const tvseries = useSelector(state => state.playlist).find(x=> parseInt(x.series_id) === parseInt(stream_id)).name;
22 | const streamsTemp = useSelector(state => state.playlistEpisodes)/*.map(x => {
23 | return {
24 | ...x,
25 | playing: parseInt(x.episode_num) === parseInt(episode)
26 | }
27 | });*/
28 |
29 | const [stream, setStream] = useState();
30 | const [streams, setStreams] = useState([]);
31 | const [streamStat, setStreamStat] = useState()
32 |
33 | useEffect(()=>{
34 | if(!streamsTemp || streamsTemp.length === 0){
35 | history.replace(`/series/`);
36 | return;
37 | }
38 | setStreams(streamsTemp.map(x => {
39 | return {
40 | ...x,
41 | playing: parseInt(x.episode_num || x.episode) === parseInt(episode),
42 | id: x.episode_num || x.episode,
43 | name: x.title
44 | }
45 | }))
46 | },[streamsTemp,episode])
47 |
48 | useEffect(()=>{
49 | if(streams && streams.length>0){
50 | const s = streams.find(x=>x.playing===true)
51 | /*if(!s.direct_source)
52 | s.direct_source = generateUrl("series", s.stream, s.container_extension)*/
53 |
54 | let stat = DB.findOne("series",stream_id);
55 | (!stat || (stat && (parseInt(stat.season) !== parseInt(season) || parseInt(stat.episode) !== parseInt(episode)))) && (stat={id: stream_id, season: season, episode: s.id, start:0, tot:0})
56 | setStreamStat(stat)
57 | DB.set("series",stream_id, stat)
58 |
59 | setStream(s);
60 | }
61 | },[streams])
62 |
63 | const setStat = (stat) =>{
64 | setStreamStat(stat);
65 | DB.set("series",stream_id, stat)
66 | }
67 |
68 | return (
69 |
70 | history.goBack()}
76 | FullPlayer
77 | autoPlay
78 | startPosition={streamStat ? parseInt(streamStat.start) : 0}
79 | overlayEnabled
80 | autoControllCloseEnabled
81 | Style
82 | primaryColor="var(--second-color)"
83 | secundaryColor="var(--first-color)"
84 | reprodutionList={streams}
85 | playlistTitle = {"Season " + season}
86 | syncDuration = {(duration, percentage) => setStat({...streamStat, start:duration, tot : parseInt(percentage), episode: episode, season: season})}
87 |
88 | dataNext={()=>{
89 | let next = streams.find(x=>x.episode_num > stream.episode_num);
90 | return { title: next ? `Next: ${next.title}` : ""}
91 | }}
92 |
93 | onClickItemListReproduction={(id, playing) => {
94 | let next = streams.find(x=>x.episode_num === id);
95 | history.replace(`/series/category/${category}/${stream_id}/play/season/${season}/episode/${next.episode_num}/`)
96 | }}
97 |
98 | onEnded={() => {
99 | if(stream){
100 | let next = streams.find(x=>x.episode_num > stream.episode_num);
101 | if(!next)
102 | return history.back();
103 | else history.replace(`/series/category/${category}/${stream_id}/play/season/${season}/episode/${next.episode_num}/`)
104 | }
105 | }}
106 |
107 | />
108 |
109 | )
110 | }
111 |
112 | export default PlayerSeries
113 |
--------------------------------------------------------------------------------
/src/components/Group/Groups.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from "react"
2 | import "./Groups.css"
3 | import styled from "styled-components"
4 | import {useSelector} from "react-redux"
5 | import {useHistory, useParams} from "react-router-dom";
6 | import {getGroup} from "../../other/last-opened-mode"
7 |
8 |
9 |
10 | const Input = styled.input`
11 | &:focus{
12 | border-color: var(--second-color);
13 | box-shadow: 0 0 0 0.2rem var(--second-color-shadow);
14 | transition: all .5s ease;
15 | }`
16 |
17 |
18 | const Groups = () => {
19 | const [navbar,setNavbar] = useState(false)
20 | const [searchValue, setSearchValue] = useState("");
21 |
22 | const history = useHistory();
23 | const { playingMode, category } = useParams();
24 |
25 | const groupsTemp = useSelector(state => state.groupsList);
26 | const [groups, setGroups] = useState(groupsTemp)
27 |
28 | const closeBar = () => {
29 | setNavbar(false)
30 | setTimeout(()=> history.goBack(),500);
31 | }
32 |
33 | useEffect(()=>{
34 | setNavbar(isNaN(category))
35 | },[category])
36 |
37 | const searchGroup = (text) =>{
38 | setSearchValue(text);
39 | setGroups(text ? groupsTemp.filter(x=> x.category_name.toLowerCase().includes(text.toLowerCase())) : groupsTemp)
40 | }
41 |
42 | useEffect(()=>{
43 | setSearchValue()
44 | setGroups(groups)
45 | },[groupsTemp])
46 |
47 | const selectGroup = (gp) =>{
48 | setNavbar(false);
49 | if(gp===-1 && playingMode !== "live")
50 | setTimeout(()=>history.replace(`/${playingMode}/`), 500);
51 | else{
52 | setGroups(groups[gp].category_id)
53 | setTimeout(()=>history.replace(`/${playingMode}/category/${groups[gp].category_id}/`), 500);
54 | }
55 | }
56 |
57 |
58 | useEffect(() => {
59 | if(!groups || groups.length===0)
60 | history.replace("/")
61 | }, []);
62 |
63 | return (
64 | <>
65 |
66 |
67 |
68 |
69 |
70 | < Input className = "form-control"
71 | type = "text"
72 | spellCheck = {false}
73 | placeholder = "Search category..."
74 | onChange = {(e) => searchGroup(e.target.value)}
75 | value = {searchValue}
76 | />
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | {playingMode !== "live" && (
86 | -
87 |
95 |
96 | )}
97 | {groups && Array.isArray(groups) && (groups.map((gp,id) => (
98 | -
99 |
107 |
108 | )))}
109 |
110 |
111 |
112 |
113 | >
114 | )
115 | }
116 |
117 | export default Groups
118 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/ProgrammeVIRTUAL.js.BK:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { useState, useEffect } from 'react'
4 | import {useSelector, useDispatch} from "react-redux"
5 | import Popup from "./Popup"
6 | import {setEpgPopup} from "../../actions/epgPopup"
7 |
8 | const dateFormat = require("dateformat");
9 |
10 | const Container = styled.li`
11 | position: absolute;
12 | left: 0;
13 | height: 100%;
14 | background-color: #fff;
15 | transition: all 0.3s ease 0s;
16 |
17 | &:hover, &:focus{
18 | z-index: 2;
19 | width: fit-content !important;
20 | max-width: 600px;
21 | text-overflow: ellipsis;
22 | white-space: nowrap;
23 | overflow: hidden;
24 | outline-width: 0px;
25 | }
26 |
27 | &:focus{
28 | border: 1px solid #0071ff;
29 | box-shadow: 0 2px 4px 0 rgb(0 0 0 / 6%);
30 | outline-width: 0px;
31 | border-radius: .25rem;
32 | }
33 |
34 | & > div{
35 | text-align: initial;
36 | position: relative;
37 | color: #303030;
38 | background-color: #f0f0f0;
39 | margin-right: 2px;
40 | margin-left: 2px;
41 | height: 100%;
42 | border-radius: .25rem;
43 | transition: all 0.3s ease 0s;
44 |
45 |
46 | &:hover, &:focus{
47 | cursor: pointer;
48 | border: 1px solid #0071ff;
49 | box-shadow: 0 2px 4px 0 rgb(0 0 0 / 6%);
50 | outline-width: 0px;
51 | }
52 | }
53 |
54 | & > div > div{
55 | display: flex;
56 | flex-direction: row;
57 | flex-wrap: nowrap;
58 | height: 100%;
59 | }
60 | `
61 | const BodyData = styled.div`
62 | padding: 1rem 1.5rem;
63 | height: 100%;
64 |
65 | text-overflow: ellipsis;
66 | white-space: nowrap;
67 | overflow: hidden;
68 | `
69 |
70 | const Title = styled.div`
71 | margin: 0;
72 | font-size: 1rem;
73 | font-weight: 500;
74 | line-height: 1.125rem;
75 |
76 | text-overflow: ellipsis;
77 | white-space: nowrap;
78 | overflow: hidden;
79 | `
80 |
81 | const Description = styled.div`
82 | padding-top: .25rem;
83 | font-size: .875rem;
84 | max-width: 500px;
85 | flex-wrap: nowrap!important;
86 |
87 | text-overflow: ellipsis;
88 | white-space: nowrap;
89 | overflow: hidden;
90 | `
91 |
92 | const Time = styled.div`
93 | padding-top: .75rem;
94 | font-size: .875rem;
95 |
96 | text-overflow: ellipsis;
97 | white-space: nowrap;
98 | overflow: hidden;
99 | `
100 | const ProgressBar = styled.div`
101 | position: absolute;
102 | top: 0;
103 | bottom: 0;
104 | left: 0;
105 | right: 0;
106 | `
107 |
108 | const Progress = styled.div`
109 | background: repeating-linear-gradient(45deg,rgba(0,0,0,.5),rgba(0,0,0,.5) 1px,#b4c6df 1px,#b4c6df 7px);
110 | opacity: .2;
111 | height: 100%;
112 | `
113 |
114 | const isActive = {
115 | color: "#fff",
116 | backgroundColor: "#064497"
117 | }
118 |
119 | const Programme = ({start, stop, title, description, dayTime, style}) => {
120 | const dispatch = useDispatch();
121 |
122 | const timer = useSelector(state => state.timer60);
123 | const h24Format = useSelector(state => state.h24);
124 | /*
125 | length = each minute is 0.25px;
126 | start = each minute is 2.73px
127 | */
128 | const [activeLength, setActiveLenght] = useState(stop > timer && start <= timer ? ((timer-start)/(stop-start))*100 : false);
129 | const lengthStyle = ((stop-start)/3600000)*15;
130 | const startStyle = ((start-dayTime)/3600000)*15;
131 | const Spacing = {
132 | minWidth : lengthStyle + "rem",
133 | width : lengthStyle + "rem",
134 | transform: `translateX(${startStyle}rem)`
135 | }
136 |
137 |
138 | useEffect(() => {
139 | if(!(stop > timer && start <= timer)){
140 | if(activeLength)
141 | setActiveLenght(false);
142 | }else setActiveLenght(((timer-start)/(stop-start))*100)
143 | }, [timer,start,stop,activeLength]);
144 |
145 |
146 | return (
147 | dispatch(setEpgPopup({title: title, description: description, start:start, stop:stop}))} className={activeLength > 0 ? "isActive" : "noactive"}>
148 | 0 ? isActive : {}}>
149 |
150 |
151 | {title}
152 | {description}
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | )
162 | }
163 |
164 | export default Programme
165 |
--------------------------------------------------------------------------------
/src/components/Group-old/Groups.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useRef} from 'react'
2 | import styled from "styled-components"
3 | import {useSelector,useDispatch} from "react-redux"
4 | import {useHistory, useParams} from "react-router-dom";
5 | import {setGroup, getGroup} from "../../other/last-opened-mode"
6 |
7 | import "../../other/Transition.css"
8 |
9 | const Background = styled.div`
10 | width:100vw;
11 | height:100vh;
12 | background-color: transparent;
13 | right:0;
14 | top:0;
15 | position: absolute;
16 | z-index:100;
17 | transition: all 1s ease;
18 | `
19 |
20 | const Container = styled.div`
21 | position: absolute;
22 | height:100vh;
23 | width:75vw;
24 | right:0;
25 | top:0;
26 | transition: all .5s ease;
27 | transform: translateX(75vw);
28 | `
29 |
30 | const SmokeEffect = styled.div`
31 | position: absolute;
32 | height:100vh;
33 | width:25vw;
34 | background-image: linear-gradient(to left, #212121 0% , transparent);
35 | right:50vw;
36 | top:0;
37 | z-index:11;
38 | `
39 |
40 | const GroupContainer = styled.div`
41 | position: absolute;
42 | background-color:#212121;
43 | right:0vw;
44 | top:0;
45 | height:100vh;
46 | width:50vw;
47 | z-index:15;
48 | vertical-align: middle;
49 | padding: 1rem 0;
50 | `
51 |
52 | const GroupList = styled.ul`
53 | right:1rem;
54 | list-style: none;
55 | display: table-cell;
56 | vertical-align: middle;
57 | min-height:calc(100vh - 5rem);
58 | height:calc(100vh - 5rem);
59 | width:45vw;
60 | &:focus{
61 | outline-width: 0px;
62 | }
63 | `
64 |
65 | const GroupScroller = styled.div`
66 | height:calc(100vh - 7rem);
67 | padding: 1rem 1.5rem;
68 | overflow:auto;
69 | `
70 |
71 | const GroupItem = styled.li`
72 | display: block;
73 | color:white;
74 | text-align: end;
75 | font-size: 1.5rem;
76 | border-radius: 5px;
77 | overflow: hidden;
78 | text-overflow: ellipsis;
79 | text-decoration: none;
80 | padding-right: 1rem;
81 | transition: all .15s ease;
82 |
83 | &:hover, &:focus{
84 | outline-width: 0px;
85 | cursor: pointer;
86 | background-color: #fff;
87 | color: #020d18;
88 | }
89 | `
90 |
91 | const Title = styled.h1`
92 | color:white;
93 | text-align: center;
94 | `
95 |
96 | const CloseMessage = styled.h5`
97 | color:white;
98 | text-align: center;
99 | `
100 |
101 |
102 | const ClosePopup = styled.a`
103 | color:white;
104 | position:absolute;
105 | right:49vw;
106 | margin-bottom: .5rem;
107 | font-weight: 500;
108 | line-height: 1.2;
109 | font-size: 2.5rem;
110 | transition: all .15s ease;
111 |
112 | &:hover{
113 | color: #020d18;
114 | cursor: pointer;
115 | }
116 | `
117 |
118 | let timeout;
119 | const Groups = () => {
120 | const gpContainerRef = useRef(null)
121 | const backGroundRef = useRef(null)
122 | const dispatch = useDispatch()
123 | const history = useHistory();
124 | const { playingMode } = useParams();
125 | const category = getGroup();
126 |
127 | const groups = useSelector(state => state.groupsList);
128 |
129 |
130 | const selectGroup = (gp) =>{
131 | setGroup(groups[gp].category_id)
132 | history.replace(`/${playingMode}/category/${groups[gp].category_id}/`);
133 | }
134 |
135 | useEffect(() => {
136 | if(!groups || groups.length==0)
137 | history.replace("/")
138 | setTimeout(()=>{
139 | backGroundRef.current && (backGroundRef.current.style.backgroundColor="#00000085");
140 | gpContainerRef.current && (gpContainerRef.current.style.transform="translateX(0)");
141 | }, 100);
142 | }, []);
143 |
144 | const closePopup = () =>{
145 | backGroundRef.current.style.backgroundColor="transparent";
146 | gpContainerRef.current.style.transform="translateX(75vw)";
147 | setTimeout(()=>{
148 | history.replace(`/${playingMode}/category/${category}/`);
149 | }, 150);
150 | }
151 |
152 | return (
153 |
154 |
155 |
156 |
157 |
158 | Choose a Category
159 |
160 |
161 | {groups.map((gp,id) => (
162 | selectGroup(id)}
166 | >{gp.category_name}
167 | ))}
168 |
169 |
170 | {/*window.gSTB && (Press to close)*/}
171 |
172 |
173 |
174 |
175 | )
176 | }
177 |
178 | export default Groups
179 |
--------------------------------------------------------------------------------
/src/components/Vod/RowItem.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react'
2 | import styled from 'styled-components'
3 | import {optimizeName} from "../../other/vod-series-name-optimizer"
4 | import PopupHover from "./PopupHover"
5 | import {Link} from "react-router-dom";
6 | import DB from "../../other/local-db"
7 | import {generateUrl} from "../../other/generate-url"
8 |
9 | const Li = styled(Link)`
10 | height: calc(15vw * 1.5);
11 | max-height: 60vh;
12 | text-align: center;
13 | flex: 0 0 15.266667%;
14 | max-width: 15.266667%;
15 | padding-left: .4rem;
16 | padding-right: .3rem;
17 | cursor: pointer;
18 | outline-width: 0;
19 | text-decoration: none !important;
20 |
21 | &:focus > div {
22 | box-shadow: rgb(255 0 0 / 75%) 0px 3px 10px;
23 | outline-width: 0;
24 | }
25 | `
26 |
27 | const ImgContainer = styled.div`
28 | height: calc(100% - 1.5rem);
29 |
30 | width: 100%;
31 | background-size: cover;
32 | background-repeat: no-repeat;
33 | background-position: 50%;
34 | border-radius: .5rem;
35 | position: relative;
36 |
37 | line-height: calc(15vw * 1.5);
38 | transition: box-shadow .3s ease;
39 | text-decoration: none;
40 |
41 | &:hover, &:focus {
42 | box-shadow: rgb(255 0 0 / 75%) 0px 3px 10px;
43 | outline-width: 0;
44 |
45 | }
46 | `
47 |
48 | const Title = styled.label`
49 | font-size: 1.2rem;
50 | padding: .1rem;
51 | line-height: 1.6rem;
52 | overflow: hidden;
53 | color:white;
54 | `
55 |
56 | const Bar = styled.div`
57 | position: absolute;
58 | display: flex;
59 | height: 4%;
60 | bottom: -5%;
61 | width: calc(100% - .7rem);
62 | background-color: black;
63 | border-radius: 10rem;
64 |
65 | & > div{
66 | height:100%;
67 | border-radius: 10rem;
68 | background-color : var(--second-color);
69 | }
70 | `
71 |
72 | const Name = styled.div`
73 | height: 1.5rem;
74 | color: white;
75 | width: calc(100%);
76 | text-overflow: ellipsis;
77 | overflow: hidden;
78 | white-space: nowrap;
79 | box-shadow: none !important;
80 | text-align: center;
81 | text-decoration: none;
82 | padding-top: 0.1rem;
83 | `
84 |
85 | let timeout = null;
86 |
87 | const noCoverStyle = {
88 | display: "flex",
89 | alignItems: "center",
90 | backgroundColor: "#0c0f13",//randomColor[0],
91 | border: "0.2rem solid white",
92 | justifyContent: "center"
93 | }
94 |
95 | const RowItem = ({name, stream_icon, last, stream_id, category_id, id, style, isSeries, container_extension, existingTmdb}) => {
96 | const [noCover,setNoCover] = useState(!stream_icon);
97 | const [streamStat, setStreamStat] = useState()
98 | const [popup, setPopup] = useState(false);
99 | const [popupStyle, setPopupStyle] = useState({transform: "scale(0)"})
100 |
101 | const styleLast={
102 | position: "absolute",
103 | height: "calc(15vw * 1.5)",
104 | right: "calc(-7% - 0.4rem)",
105 | width: "100%",
106 | filter: "brightness(0.5)",
107 | pointerEvents: "none",
108 | cursor: "default",
109 | textDecoration: "none"
110 | }
111 |
112 | const showPopup = (mode) =>{
113 | clearTimeout(timeout);
114 | if(mode === true){
115 | timeout = setTimeout(()=>{
116 | setPopup(true);
117 | setTimeout(()=>{
118 | setPopupStyle({transform: "scale(1)"})
119 | },100)
120 | },700);
121 | }else {
122 | setPopupStyle({transform: "scale(0)"})
123 | setTimeout(()=>{
124 | setPopup(false);
125 | },100)
126 | }
127 | }
128 |
129 | useEffect(() => {
130 | setStreamStat(DB.findOne(isSeries ? "series" : "movie",stream_id, category_id === "fav"))
131 | }, [isSeries, stream_id, category_id])
132 |
133 |
134 | return (
135 | showPopup(true)} onMouseLeave={() => showPopup(false)}
139 | to={{
140 | pathname:`/${isSeries ? "series" : "movie"}/category/${category_id}/${stream_id}/info/`,
141 | search: window.location.search,
142 | }}
143 | >
144 |
145 | {!noCover ?
146 |
setNoCover(true)} alt={name}/>
147 | :
148 | {optimizeName(name)}
149 | }
150 |
151 | {!noCover && ({optimizeName(name)})}
152 | {streamStat && streamStat.tot > 3 && streamStat.tot < 95 && (
153 |
154 |
155 |
156 | )}
157 | {popup && !last && (
)}
158 |
159 | )
160 | }
161 |
162 | export default RowItem
--------------------------------------------------------------------------------
/src/components/Series/Seasons.js:
--------------------------------------------------------------------------------
1 | import React, {useState,useEffect} from 'react'
2 | import styled from "styled-components"
3 | import Episode from './Episode'
4 | import {useDispatch} from "react-redux"
5 | import {setPlaylistEpisodes} from "../../actions/set-Playlist-Episodes"
6 | import {useHistory, useLocation, useParams} from "react-router-dom";
7 | import DB from "../../other/local-db"
8 | import {generateUrl} from "../../other/generate-url"
9 |
10 | const Select = styled.select`
11 | display: flex;
12 | align-items: center;
13 | min-width: 4em;
14 | position: relative;
15 | font-size: 1.3rem;
16 | background-color: rgb(36, 36, 36);
17 | color: white;
18 | cursor: pointer;
19 | border: 0.1em solid rgb(77, 77, 77);
20 | border-radius: 0.2em;
21 | outline: 0;
22 | font-weight: 600;
23 | padding: 0.3rem 0.5rem;
24 |
25 | transition: all 0.2s ease;
26 |
27 | &:focus, &:hover{
28 | border-color: white;
29 | box-shadow: 0 0 0 0.2rem rgb(255 255 255 / 25%);
30 | }
31 |
32 | & > option{
33 |
34 | }
35 | `
36 |
37 | const Container = styled.div`
38 |
39 | margin-top:1rem;
40 | `
41 |
42 | const Seasons = ({seasonData, cover}) => {
43 | const [seasons,setSeasons] = useState([])
44 | const [streamStat, setStreamStat] = useState({})
45 | const [selectedSeason, setSelectedSeason] = useState();
46 |
47 | const {stream_id} = useParams();
48 |
49 | const history = useHistory();
50 | const location = useLocation();
51 | const dispatch = useDispatch();
52 |
53 | useEffect(()=>{
54 | if(!seasonData)
55 | return;
56 | const s = Object.keys(seasonData).map(x => {
57 | return {
58 | id: parseInt(x),
59 | name: "Season " + x,
60 | episodes: seasonData[x].map(y => {
61 | return {
62 | ...y,
63 | episode_num: parseInt(y.episode_num),
64 | url: y.url || y.direct_source || generateUrl("series", y.id, y.container_extension),
65 | url2: generateUrl("series", y.id, y.container_extension)
66 | }
67 | })
68 | }
69 | });
70 | if(s.length > 0){
71 | setSeasons(s);
72 |
73 | let stat = DB.findOne("series",stream_id);
74 | (!stat && (stat={id: stream_id, season: s[0].id, episode: s[0].episodes[0].episode_num, start:0}))
75 |
76 | stat.season = parseInt(stat.season);
77 | stat.episode = parseInt(stat.episode);
78 |
79 | if(stat.tot>95 || stat.tot< 3){
80 | stat.tot = 0;
81 | let season = s.find(x=>x.id === stat.season);
82 |
83 | if(season.episodes[season.episodes.length-1].episode_num > stat.episode){
84 | stat.episode++;
85 | }else{
86 | season = s.find(x=>x.id === stat.season+1);
87 | if(season && season.episodes && season.episodes.length >0){
88 | stat.season = season.id;
89 | stat.episode = season.episodes[0].episode_num
90 | }else stat={id: stream_id, season: s[0].id, episode: s[0].episodes[0].episode_num, start:0}
91 | }
92 | }
93 |
94 | setSelectedSeason(stat.season);
95 | setStreamStat(stat)
96 | }
97 | }, [seasonData, stream_id])
98 |
99 | const playEpisode = (episode) =>{
100 | const list = seasons.find(x=>x.id === selectedSeason).episodes.map(x=> {return{...x, id:x.info.id}});
101 | dispatch(setPlaylistEpisodes(list))
102 | history.push(location.pathname.replace("info",`play/season/${selectedSeason}/episode/${episode}`))
103 | }
104 |
105 | return (
106 | <>
107 |
108 |
109 |
Episodes
110 |
111 |
112 |
115 |
116 |
117 |
118 | {streamStat && seasons.find(x=> x.id === selectedSeason) && (
119 | seasons.find(x=> x.id === selectedSeason).episodes.map(ep=>
120 |
132 | )
133 | )}
134 |
135 | >
136 | )
137 | }
138 |
139 | export default Seasons
140 |
--------------------------------------------------------------------------------
/src/components/Epg-Fullscreen/Programme.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { useState, useEffect } from 'react'
4 | import {useSelector, useDispatch} from "react-redux"
5 | import {setEpgPopup} from "../../actions/epgPopup"
6 |
7 | const dateFormat = require("dateformat");
8 |
9 | const Container = styled.li`
10 | position: absolute;
11 | left: 0;
12 | height: 100%;
13 | background-color: #fff;
14 | transition: all 0.3s ease 0s;
15 |
16 | &:hover, &:focus{
17 | /*z-index: 2;
18 | width: fit-content !important;
19 | max-width: 600px;
20 | text-overflow: ellipsis;
21 | white-space: nowrap;
22 | overflow: hidden;*/
23 | outline-width: 0px;
24 | }
25 |
26 | &:focus{
27 | border: 1px solid #0071ff;
28 | box-shadow: 0 2px 4px 0 rgb(0 0 0 / 6%);
29 | outline-width: 0px;
30 | border-radius: .25rem;
31 | }
32 |
33 | & > div{
34 | text-align: initial;
35 | position: relative;
36 | color: #303030;
37 | background-color: #f0f0f0;
38 | margin-right: 2px;
39 | margin-left: 2px;
40 | height: 100%;
41 | border-radius: .25rem;
42 | transition: all 0.3s ease 0s;
43 |
44 |
45 | &:hover, &:focus{
46 | cursor: pointer;
47 | border: 1px solid #0071ff;
48 | box-shadow: 0 2px 4px 0 rgb(0 0 0 / 6%);
49 | outline-width: 0px;
50 | }
51 | }
52 |
53 | & > div > div{
54 | display: flex;
55 | flex-direction: row;
56 | flex-wrap: nowrap;
57 | height: 100%;
58 | }
59 | `
60 | const BodyData = styled.div`
61 | padding: 1rem 1.5rem;
62 | height: 100%;
63 |
64 | text-overflow: ellipsis;
65 | white-space: nowrap;
66 | overflow: hidden;
67 | `
68 |
69 | const Title = styled.div`
70 | margin: 0;
71 | font-size: 1rem;
72 | font-weight: 500;
73 | line-height: 1.125rem;
74 |
75 | text-overflow: ellipsis;
76 | white-space: nowrap;
77 | overflow: hidden;
78 | `
79 |
80 | const Description = styled.div`
81 | padding-top: .25rem;
82 | font-size: .875rem;
83 | max-width: 500px;
84 | flex-wrap: nowrap!important;
85 |
86 | text-overflow: ellipsis;
87 | white-space: nowrap;
88 | overflow: hidden;
89 | `
90 |
91 | const Time = styled.div`
92 | padding-top: .75rem;
93 | font-size: .875rem;
94 |
95 | text-overflow: ellipsis;
96 | white-space: nowrap;
97 | overflow: hidden;
98 | `
99 | const ProgressBar = styled.div`
100 | position: absolute;
101 | top: 0;
102 | bottom: 0;
103 | left: 0;
104 | right: 0;
105 | `
106 |
107 | const Progress = styled.div`
108 | background: repeating-linear-gradient(45deg,rgba(0,0,0,.5),rgba(0,0,0,.5) 1px,#b4c6df 1px,#b4c6df 7px);
109 | opacity: .2;
110 | height: 100%;
111 | `
112 |
113 | const isActive = {
114 | color: "#fff",
115 | backgroundColor: "#064497"
116 | }
117 |
118 | const Programme = ({start, stop, title, description, dayTime, chId, liveTime, catchup}) => {
119 | const dispatch = useDispatch();
120 |
121 | const timer = useSelector(state => state.timer60);
122 | const h24Format = useSelector(state => state.h24);
123 | /*
124 | length = each minute is 0.25px;
125 | start = each minute is 2.73px
126 | */
127 |
128 | /*const startDay = new Date(dayTime);
129 | startDay.setHours(0,0,0,0)
130 | const endDay = new Date(dayTime);
131 | endDay.setHours(23,59,59,9999)
132 |
133 | start < startDay.getTime() && (start = startDay.getTime())
134 | stop > endDay.getTime() && (stop = endDay.getTime())*/
135 |
136 | const [activeLength, setActiveLenght] = useState(liveTime===true && stop > timer && start <= timer ? ((timer-start)/(stop-start))*100 : false);
137 | const lengthStyle = ((stop-start)/3600000)*17;
138 | const startStyle = -2+((start-dayTime)/3600000)*17;
139 | const Spacing = {
140 | minWidth : lengthStyle + "rem",
141 | width : lengthStyle + "rem",
142 | transform: `translateX(${startStyle}rem)`
143 | }
144 |
145 |
146 | useEffect(() => {
147 | if(liveTime === false)
148 | return;
149 | if(!(stop > timer && start <= timer)){
150 | if(activeLength)
151 | setActiveLenght(false);
152 | }else setActiveLenght(((timer-start)/(stop-start))*100)
153 | }, [timer,start,stop,activeLength, liveTime]);
154 |
155 |
156 | return (
157 | dispatch(setEpgPopup(
158 | {
159 | title: title,
160 | description: description,
161 | start:start, stop:stop,
162 | catchup: catchup,
163 | chId}
164 | ))} className={activeLength > 0 ? "isActive" : "noActive"} id={`ch${chId}-${start}`}>
165 | 0 ? isActive : {}}>
166 |
167 |
168 | {catchup > 0 ? () : ""}{title}
169 | {description}
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | )
179 | }
180 |
181 | export default Programme
182 |
--------------------------------------------------------------------------------
/src/components/Popup/Popup.js:
--------------------------------------------------------------------------------
1 | import React, {useState,useEffect} from 'react'
2 | import styled from 'styled-components'
3 |
4 | const Container = styled.div`
5 | position: fixed;
6 | top: 50%;
7 | left: 50%;
8 | transform: translate(-50%, -50%);
9 | `
10 |
11 | const Message = styled.div`
12 | padding: 0rem 2rem;
13 | color: #fff;
14 | box-shadow: 0 15px 35px rgb(50 50 93 / 20%), 0 5px 15px rgb(0 0 0 / 17%);
15 | pointer-events: auto;
16 | border: 0 solid rgba(0, 0, 0, .2);
17 | border-radius: .4375rem;
18 | outline: 0;
19 | `
20 |
21 | const Header = styled.div`
22 | border-color: rgba(255, 255, 255, .075);
23 | display: flex;
24 | padding: 1.25rem;
25 | border-bottom: 0 solid #e9ecef;
26 | border-top-left-radius: .4375rem;
27 | border-top-right-radius: .4375rem;
28 | align-items: flex-start;
29 | justify-content: space-between;
30 |
31 | & > h6{
32 | font-size: 1.0625rem;
33 | line-height: 1.1;
34 | margin-bottom: 0;
35 | }
36 |
37 | & > button{
38 | overflow: visible;
39 | text-transform: none;
40 | font-size: 1.5rem;
41 | font-weight: 600;
42 | line-height: 1;
43 | float: right;
44 | text-shadow: none;
45 | transition: all .15s ease;
46 | border: 0;
47 | appearance: none;
48 | cursor: pointer;
49 | text-decoration: none;
50 | margin: -1rem -1rem -1rem auto;
51 | padding: 1.25rem;
52 | color: rgba(0, 0, 0, .9);
53 | outline: 0;
54 | background-color: transparent;
55 | opacity: .75;
56 |
57 | & > span{
58 | color:white;
59 | }
60 |
61 | }
62 | `
63 |
64 | const Body = styled.div`
65 | position: relative;
66 | padding: 1.5rem;
67 | flex: 1 1 auto;
68 | text-align: center;
69 |
70 | & > h4{
71 | font-size: .95rem;
72 | font-weight: 600;
73 | letter-spacing: .025em;
74 | text-transform: uppercase;
75 | }
76 |
77 |
78 | & > i{
79 | font-size: 3em;
80 | line-height:5rem;
81 | }
82 | `
83 |
84 | const Footer = styled.div`
85 | display: flex;
86 | padding: 1.25rem;
87 | border-top: 0 solid #e9ecef;
88 | border-bottom-right-radius: .4375rem;
89 | border-bottom-left-radius: .4375rem;
90 | flex-wrap: wrap;
91 | align-items: center;
92 | justify-content: flex-end;
93 | border-color: rgba(255, 255, 255, .075);
94 | `
95 |
96 | const ErrorButton = styled.button`
97 | color: #212529;
98 | border-color: #fff;
99 | background-color: #e6e6e6;
100 | box-shadow: none;
101 | text-decoration: none;
102 | cursor: pointer;
103 |
104 | &:hover{
105 | background-color: #829196;
106 | color: white;
107 | border-color: #829196;
108 | box-shadow: 0 0 0 0.2rem #82919608;
109 | -webkit-transition: all .5s ease;
110 | transition: all .5s ease;
111 | background-color: var(--second-color);
112 | }
113 | `
114 |
115 | const SafeButton = styled.button`
116 | color: #212529;
117 | border-color: #fff;
118 | background-color: #e6e6e6;
119 | box-shadow: none;
120 | text-decoration: none;
121 | cursor: pointer;
122 |
123 | &:hover{
124 | background-color: #5536f5;
125 | color: white;
126 | border-color: #829196;
127 | box-shadow: 0 0 0 0.2rem #82919608;
128 | -webkit-transition: all .5s ease;
129 | transition: all .5s ease;
130 | }
131 | `
132 |
133 |
134 | const Popup = ({type,title,description,icon, onclick, error=true, unsecure}) => {
135 | const [style, setStyle] = useState({
136 | opacity: 0,
137 | transform: "scale(0.9)",
138 | background: error === true ? "linear-gradient(87deg, #f5365c 0, #f56036 100%)" : "linear-gradient(87deg, #6536f5 0px, #36bbf5 100%)"
139 | });
140 |
141 | useEffect(() => {
142 | setStyle({
143 | opacity: 1,
144 | transform: "translateX(0)",
145 | transition: "opacity 300ms, transform 300ms",
146 | background: error === true ? "linear-gradient(87deg, #f5365c 0, #f56036 100%)" : "linear-gradient(87deg, #6536f5 0px, #36bbf5 100%)"
147 | })
148 | },[]);
149 |
150 | const onCloseEvent = () =>{
151 | setStyle({
152 | opacity: 0,
153 | transform: "scale(0.9)",
154 | transition: "opacity 300ms, transform 300ms",
155 | background: error === true ? "linear-gradient(87deg, #f5365c 0, #f56036 100%)" : "linear-gradient(87deg, #6536f5 0px, #36bbf5 100%)"
156 | })
157 |
158 | setTimeout(()=>{
159 | onclick && (onclick());
160 | },150)
161 | }
162 |
163 | return (
164 |
165 |
166 |
170 |
171 |
172 | {title}
173 | {unsecure === true ?
174 | ()
175 | :
176 | ({description}
)
177 | }
178 |
179 |
186 |
187 |
188 | )
189 | }
190 |
191 | export default Popup
192 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready.then(registration => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/components/AccountInfo.js:
--------------------------------------------------------------------------------
1 | import React, {useState,useEffect} from 'react'
2 | import "./MainMenu/MainMenu"
3 | import styled from "styled-components"
4 | import {getInfo, logout} from "../other/user_info"
5 | import {useHistory} from 'react-router-dom'
6 | import {useSelector, useDispatch} from "react-redux"
7 | import {setH24} from "../actions/h24"
8 |
9 | const Container = styled.div`
10 | background-color: #212529;
11 | position:absolute;
12 | width:100%;
13 | height:100%;
14 | top:0;
15 | left:0;
16 | `
17 |
18 | const Box = styled.div`
19 | position: fixed;
20 | z-index: 98;
21 | &:before, &:after {
22 | content: "";
23 | position: fixed;
24 | width: 100vw;
25 | height: 100vh;
26 | background: rgba(20, 21, 26,0.6);
27 | border-bottom-left-radius: 200%;
28 | z-index: -1;
29 | -webkit-transition: -webkit-transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
30 | transition: -webkit-transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
31 | transition: transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
32 | transition: transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, -webkit-transform cubic-bezier(0.77, 0, 0.175, 1) 0.6s, border-radius linear 0.8s;
33 | -webkit-transform: translateX(100%) translateY(-100%);
34 | transform: translateX(100%) translateY(-100%);
35 | }
36 | &:after {
37 | background: rgba(9,9,12,1);
38 | -webkit-transition-delay: 0s;
39 | transition-delay: 0s;
40 | }
41 | &:before {
42 | -webkit-transition-delay: .2s;
43 | transition-delay: .2s;
44 | }
45 | `
46 |
47 | const Li = styled.li`
48 | font-size: 3rem;
49 | text-align: left;
50 |
51 | & > button{
52 | text-transform: none !important;
53 | }
54 |
55 | & > button:after{
56 | height: 0px !important;
57 | }
58 | `
59 |
60 | const Button = styled.button`
61 | color: #fff;
62 | background-color: var(--first-color) !important;
63 | transition: all .5s ease;
64 | width:100%;
65 |
66 | &:after{
67 | height: 0px !important;
68 | }
69 |
70 | &:hover, &:focus{
71 | background-color: #829196;
72 | color:white;
73 | border-color: #829196;
74 | box-shadow: 0 0 0 0.2rem #82919608;
75 | transition: all .5s ease;
76 | }
77 | `
78 |
79 | const ButtonLogout = styled.button`
80 | color: #fff;
81 | background-color: var(--second-color) !important;
82 | transition: all .5s ease;
83 | width:100%;
84 |
85 | &:after{
86 | height: 0px !important;
87 | }
88 |
89 | &:hover, &:focus{
90 | background-color: var(--second-color) !important;
91 | color:white;
92 | border-color: #829196;
93 | box-shadow: 0 0 0 0.2rem #82919608;
94 | transition: all .5s ease;
95 | }
96 | `
97 | const Checkbox = styled.input`
98 | font-size: 3rem;
99 | height: 3rem;
100 | width: 3rem;
101 | `
102 |
103 |
104 |
105 | const AccountInfo = () => {
106 | const [style, setStyle] = useState();
107 | const info = getInfo();
108 | const history = useHistory();
109 | const dispatch = useDispatch()
110 | const h24 = useSelector(state => state.h24)
111 |
112 | useEffect(() => {
113 | setTimeout(()=>{
114 | setStyle("nav-active");
115 | document.getElementById("info-back").focus();
116 | },10)
117 | },[]);
118 |
119 |
120 | const back = (page) =>{
121 | setStyle();
122 | setTimeout(()=>{
123 | history.goBack();
124 | },600)
125 | }
126 |
127 | return (
128 |
129 |
130 |
156 |
157 |
158 | )
159 | }
160 |
161 | export default AccountInfo
162 |
--------------------------------------------------------------------------------
/src/components/MainMenu/MainMenu.css:
--------------------------------------------------------------------------------
1 | div.nav-active .nav__content {
2 | visibility: visible;
3 | }
4 | div.nav-active .menu-icon__line {
5 | background-color: #fff;
6 | -webkit-transform: translate(0px, 0px) rotate(-45deg);
7 | transform: translate(0px, 0px) rotate(-45deg);
8 | }
9 | div.nav-active .menu-icon__line-left {
10 | width: 15px;
11 | -webkit-transform: translate(2px, 4px) rotate(45deg);
12 | transform: translate(2px, 4px) rotate(45deg);
13 | }
14 | div.nav-active .menu-icon__line-right {
15 | width: 15px;
16 | float: right;
17 | -webkit-transform: translate(-3px, -3.5px) rotate(45deg);
18 | transform: translate(-3px, -3.5px) rotate(45deg);
19 | }
20 | div.nav-active .menu-icon:hover .menu-icon__line-left,
21 | div.nav-active .menu-icon:hover .menu-icon__line-right,
22 | div.nav-active .menu-icon:focus .menu-icon__line-left,
23 | div.nav-active .menu-icon:focus .menu-icon__line-right {
24 | width: 15px;
25 | }
26 | div.nav-active .nav {
27 | visibility: visible;
28 | }
29 | div.nav-active .nav:before, div.nav-active .nav:after {
30 | -webkit-transform: translateX(0%) translateY(0%);
31 | transform: translateX(0%) translateY(0%);
32 | border-radius: 0;
33 | }
34 | div.nav-active .nav:after {
35 | -webkit-transition-delay: .1s;
36 | transition-delay: .1s;
37 | }
38 | div.nav-active .nav:before {
39 | -webkit-transition-delay: 0s;
40 | transition-delay: 0s;
41 | }
42 | div.nav-active .nav__list-item {
43 | opacity: 1;
44 | -webkit-transform: translateX(0%);
45 | transform: translateX(0%);
46 | -webkit-transition: opacity .3s ease, color .3s ease, -webkit-transform .3s ease;
47 | transition: opacity .3s ease, color .3s ease, -webkit-transform .3s ease;
48 | transition: opacity .3s ease, transform .3s ease, color .3s ease;
49 | transition: opacity .3s ease, transform .3s ease, color .3s ease, -webkit-transform .3s ease;
50 | }
51 | div.nav-active .nav__list-item:nth-child(0) {
52 | -webkit-transition-delay: 0.7s;
53 | transition-delay: 0.7s;
54 | }
55 | div.nav-active .nav__list-item:nth-child(1) {
56 | -webkit-transition-delay: 0.8s;
57 | transition-delay: 0.8s;
58 | }
59 | div.nav-active .nav__list-item:nth-child(2) {
60 | -webkit-transition-delay: 0.9s;
61 | transition-delay: 0.9s;
62 | }
63 | div.nav-active .nav__list-item:nth-child(3) {
64 | -webkit-transition-delay: 1s;
65 | transition-delay: 1s;
66 | }
67 | div.nav-active .nav__list-item:nth-child(4) {
68 | -webkit-transition-delay: 1.1s;
69 | transition-delay: 1.1s;
70 | }
71 | div.nav-active .nav__list-item:nth-child(5) {
72 | -webkit-transition-delay: 1.2s;
73 | transition-delay: 1.2s;
74 | }
75 | div.nav-active .nav__list-item:nth-child(6) {
76 | -webkit-transition-delay: 1.3s;
77 | transition-delay: 1.3s;
78 | }
79 | div.nav-active .nav__list-item:nth-child(7) {
80 | -webkit-transition-delay: 1.4s;
81 | transition-delay: 1.4s;
82 | }
83 | div.nav-active .nav__list-item:nth-child(8) {
84 | -webkit-transition-delay: 1.5s;
85 | transition-delay: 1.5s;
86 | }
87 | div.nav-active .nav__list-item:nth-child(9) {
88 | -webkit-transition-delay: 1.6s;
89 | transition-delay: 1.6s;
90 | }
91 | div.nav-active .nav__list-item:nth-child(10) {
92 | -webkit-transition-delay: 1.7s;
93 | transition-delay: 1.7s;
94 | }
95 |
96 |
97 |
98 |
99 |
100 | .nav__content {
101 | position: fixed;
102 | visibility: hidden;
103 | top: 50%;
104 | margin-top: 20px;
105 | -webkit-transform: translate(0%, -50%);
106 | transform: translate(0%, -50%);
107 | width: 100%;
108 | text-align: center;
109 | }
110 | .nav__list {
111 | position: relative;
112 | padding: 0;
113 | margin: 0;
114 | z-index: 2;
115 | }
116 | .nav__list-item {
117 | position: relative;
118 | display: block;
119 | -webkit-transition-delay: 0.8s;
120 | transition-delay: 0.8s;
121 | opacity: 0;
122 | text-align: center;
123 | color: #fff;
124 | overflow: hidden;
125 | font-size: 5rem;
126 | font-weight: 900;
127 | line-height: 1.15;
128 | letter-spacing: 3px;
129 | -webkit-transform: translate(100px, 0%);
130 | transform: translate(100px, 0%);
131 | -webkit-transition: opacity .2s ease, -webkit-transform .3s ease;
132 | transition: opacity .2s ease, -webkit-transform .3s ease;
133 | transition: opacity .2s ease, transform .3s ease;
134 | transition: opacity .2s ease, transform .3s ease, -webkit-transform .3s ease;
135 | margin-top: 0;
136 | margin-bottom: 0;
137 | }
138 | .nav__list-item button{
139 | background-color: transparent;
140 | border: none;
141 | outline: none;
142 |
143 | position: relative;
144 | text-decoration: none;
145 | color: rgba(255,255,255,0.6);
146 | overflow: hidden;
147 | cursor: pointer;
148 | padding-left: 5px;
149 | padding-right: 5px;
150 | font-weight: 900;
151 | z-index: 2;
152 | display: inline-block;
153 | text-transform: uppercase;
154 | -webkit-transition: all 200ms linear;
155 | transition: all 200ms linear;
156 | }
157 | .nav__list-item button:after{
158 | position: absolute;
159 | content: '';
160 | top: 50%;
161 | margin-top: -2px;
162 | left: 50%;
163 | width: 0;
164 | height: 0;
165 | opacity: 0;
166 | background-color: var(--second-color);
167 | z-index: 1;
168 | -webkit-transition: all 200ms linear;
169 | transition: all 200ms linear;
170 | }
171 |
172 | .nav__list-item{
173 | outline-width: 0;
174 | }
175 |
176 | .nav__list-item button:hover:after,
177 | .nav__list-item:focus button:after{
178 | height: 4px;
179 | opacity: 1;
180 | left: 0;
181 | width: 100%;
182 | }
183 | .nav__list-item button:hover,
184 | .nav__list-item:focus button{
185 | color: rgba(255,255,255,1);
186 | }
187 | .nav__list-item.active-nav button{
188 | color: rgba(255,255,255,1);
189 | }
190 | .nav__list-item.active-nav button:after{
191 | height: 4px;
192 | opacity: 1;
193 | left: 0;
194 | width: 100%;
195 | }
--------------------------------------------------------------------------------
/src/components/Login.js:
--------------------------------------------------------------------------------
1 | import React, {useState,useRef,useEffect} from 'react'
2 | import styled from 'styled-components';
3 | import {useHistory} from "react-router-dom";
4 | import {useAuth} from "../other/auth"
5 | import Popup from "./Popup/Popup"
6 | import Cookies from 'js-cookie'
7 |
8 | const Container = styled.div`
9 | position:absolute;
10 | width:100%;
11 | height:100%;
12 | top:0;
13 | left:0;
14 | background-color: var(--first-color);
15 | transition: all .5s ease;
16 | overflow:hidden;
17 |
18 | & > * >input{
19 | background-color: var(--first-color);
20 | color:white;
21 | }
22 | `
23 |
24 | const Box = styled.form`
25 | width: 50vw;
26 | left: 25vw;
27 | top: 25vh;
28 | position: absolute;
29 |
30 | & > h2, & > h5{
31 | color:white;
32 | text-align: center;
33 | }
34 |
35 | & > label{
36 | color:white;
37 | }
38 | `
39 |
40 | const Input = styled.input`
41 | margin-bottom: 1rem;
42 |
43 | &:focus{
44 | color:white;
45 | border-color: var(--second-color);
46 | box-shadow: 0 0 0 0.2rem var(--second-color-shadow);
47 | background-color: var(--first-color);
48 | transition: all .5s ease;
49 | }
50 | `
51 |
52 | const Button = styled.button`
53 | color: #fff;
54 | background-color: var(--second-color);
55 | transition: all .5s ease;
56 | width:100%;
57 |
58 | &:hover, &:focus{
59 | background-color: #829196;
60 | color:white;
61 | border-color: #829196;
62 | box-shadow: 0 0 0 0.2rem #82919608;
63 | transition: all .5s ease;
64 | }
65 | `
66 |
67 | const Login = ({url}) => {
68 |
69 | const [dns, setDns] = useState("");
70 | const [username, setUsername] = useState("");
71 | const [password, setPassword] = useState("");
72 | const [showPopup, setShowPopup] = useState(false);
73 | const [blur, setBlur] = useState();
74 | const [m3u8, setM3u8] = useState(window.m3u8warning === true && !Cookies.get("m3u8_play"));
75 |
76 | const history = useHistory();
77 | const auth = useAuth();
78 |
79 | useEffect(() => {
80 | if(auth.isAuth())
81 | history.push(url||"/")
82 | else auth.authLogin(()=>history.push(url||"/"))
83 | },[auth,history]);
84 |
85 | useEffect(()=>{
86 | m3u8 ? setBlur({filter:"blur(.3rem)"}) : setBlur({});
87 | },[m3u8])
88 |
89 | const inputRef = useRef(0)
90 |
91 | const remoteController = (event) => {
92 | let active = document.activeElement;
93 | if (event.keyCode === 40 && active.nextSibling) {
94 | if(active.nextSibling.tagName==="LABEL")
95 | active = active.nextSibling;
96 | active.nextSibling.focus();
97 | } else if (event.keyCode === 38 && active.previousSibling) {
98 | if(active.previousSibling.tagName==="LABEL")
99 | active = active.previousSibling;
100 | active.previousSibling.focus();
101 | } else if (event.keyCode === 13)
102 | active.click();
103 | }
104 |
105 | const login = (e) =>{
106 | e.preventDefault();
107 | setBlur({filter:"blur(.3rem)"})
108 | auth.signin(dns ,username,password,
109 | () => window.location="/"//history.replace("/")
110 | ,
111 | (title,description) => {
112 | setShowPopup({title:title,description:description});
113 | }
114 | )
115 | }
116 |
117 | const closePopup = () =>{
118 | setBlur({filter:"blur(0)"});
119 | setShowPopup(false);
120 | inputRef.current.focus();
121 | }
122 |
123 | return (
124 | <>
125 |
126 |
127 | {false && (Welcome to {window.playername}
)}
128 | 
129 |
130 | Write your credentials to continue
131 |
132 | {!window.dns &&
133 | ([
134 | ,
135 | setDns(e.target.value)} value={dns} />
136 | ])
137 | }
138 |
139 | setUsername(e.target.value)} value={username} />
140 |
141 | setPassword(e.target.value)} value={password}/>
142 |
143 |
144 |
145 | {showPopup && }
146 | {m3u8 && (
147 | IPTVEditor Web Player can play live channels streams only in M3U8 format.
The conversion will be done automatically if streams are in Xtreamcodes format (this won't affect your playlist)."} icon={"fas fa-info-circle"} onclick={()=> {Cookies.set("m3u8_play",1,{ expires: 365 }); setM3u8(!m3u8);}}/>
148 | )}
149 | {/*showKeyboard !== false && isMag && ()*/}
150 | >
151 | )
152 | }
153 |
154 | export default Login
155 |
--------------------------------------------------------------------------------