essai / streamlit_app.py
Guymy97's picture
Update via AnyCoder - streamlit_app.py
164ec2b verified
import streamlit as st
import streamlit.components.v1 as components
# --- Page Configuration ---
st.set_page_config(
page_title="TiviStream Pro",
page_icon="📺",
layout="wide",
initial_sidebar_state="collapsed"
)
# --- CSS to remove Streamlit clutter ---
st.markdown("""
<style>
/* Hide Streamlit header, footer, and hamburger menu */
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
header {visibility: hidden;}
/* Remove padding to allow full-screen iframe */
.block-container {
padding-top: 0rem;
padding-bottom: 0rem;
padding-left: 0rem;
padding-right: 0rem;
}
/* Ensure the iframe container takes full height */
iframe {
height: 100vh !important;
}
</style>
""", unsafe_allow_html=True)
# --- Header Link ---
st.markdown(
"""
<div style="position: absolute; top: 10px; right: 10px; z-index: 9999;">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" style="color: rgba(255,255,255,0.5); text-decoration: none; font-family: sans-serif; font-size: 12px; background: rgba(0,0,0,0.5); padding: 5px 10px; border-radius: 15px;">Built with anycoder</a>
</div>
""",
unsafe_allow_html=True
)
# --- Main Application Logic ---
# We embed the sophisticated HTML/JS app here.
# This provides a much smoother "TV-like" experience than standard Streamlit widgets.
html_code = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TiviStream Pro</title>
<!-- Fonts & Icons -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--bg-dark: #0b0d12;
--bg-glass: rgba(15, 23, 42, 0.85);
--bg-panel: rgba(30, 41, 59, 0.7);
--primary: #3b82f6;
--primary-hover: #2563eb;
--accent: #f59e0b;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--border: rgba(255, 255, 255, 0.08);
--focus-bg: rgba(255, 255, 255, 0.1);
--transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-font-smoothing: antialiased;
}
body {
font-family: 'Inter', sans-serif;
background-color: #000;
color: var(--text-main);
height: 100vh;
width: 100vw;
overflow: hidden;
}
/* --- Layers --- */
#video-layer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
video {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
#ui-layer {
position: relative;
z-index: 10;
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(to right, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.7) 40%, rgba(0,0,0,0) 100%);
transition: opacity 0.3s ease;
}
/* --- Login Modal --- */
#login-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
background: rgba(0,0,0,0.85);
backdrop-filter: blur(15px);
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.4s ease;
}
#login-modal.hidden {
opacity: 0;
pointer-events: none;
}
.login-container {
background: #1e293b;
width: 600px;
border-radius: 16px;
border: 1px solid var(--border);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
overflow: hidden;
display: flex;
flex-direction: column;
}
.login-header {
padding: 20px 30px;
background: rgba(0,0,0,0.2);
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.login-tabs {
display: flex;
background: rgba(0,0,0,0.3);
padding: 5px;
}
.tab-btn {
flex: 1;
padding: 15px;
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
font-weight: 600;
transition: var(--transition);
border-bottom: 2px solid transparent;
}
.tab-btn.active {
color: var(--primary);
border-bottom-color: var(--primary);
background: rgba(59, 130, 246, 0.05);
}
.login-body {
padding: 30px;
}
.input-group {
margin-bottom: 20px;
}
.input-group label {
display: block;
margin-bottom: 8px;
color: var(--text-muted);
font-size: 0.9rem;
}
.form-input {
width: 100%;
padding: 12px 15px;
background: rgba(0,0,0,0.3);
border: 1px solid var(--border);
border-radius: 8px;
color: white;
font-size: 1rem;
outline: none;
transition: var(--transition);
}
.form-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.btn-primary {
width: 100%;
padding: 14px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.btn-primary:hover {
background: var(--primary-hover);
transform: translateY(-1px);
}
/* --- Main UI --- */
header {
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.brand {
font-size: 1.5rem;
font-weight: 700;
color: white;
display: flex;
align-items: center;
gap: 12px;
}
.brand span {
background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.top-meta {
display: flex;
gap: 20px;
align-items: center;
font-size: 0.9rem;
color: var(--text-muted);
}
.connection-badge {
background: rgba(16, 185, 129, 0.1);
color: #10b981;
padding: 4px 10px;
border-radius: 20px;
border: 1px solid rgba(16, 185, 129, 0.2);
font-size: 0.75rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
}
.content-area {
display: flex;
flex: 1;
overflow: hidden;
padding-bottom: 20px;
}
/* Sidebar */
.sidebar {
width: 280px;
padding: 10px 0;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
background: rgba(0,0,0,0.2);
}
.group-item {
padding: 12px 25px;
color: var(--text-muted);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: var(--transition);
border-left: 3px solid transparent;
}
.group-item:hover {
background: var(--focus-bg);
color: white;
}
.group-item.active {
background: linear-gradient(90deg, rgba(59, 130, 246, 0.1), transparent);
color: white;
border-left-color: var(--primary);
font-weight: 600;
}
/* EPG List */
.epg-container {
flex: 1;
display: flex;
flex-direction: column;
padding-left: 20px;
position: relative;
}
.channel-list {
overflow-y: auto;
padding-right: 20px;
mask-image: linear-gradient(to bottom, black 90%, transparent 100%);
}
.channel-row {
display: grid;
grid-template-columns: 60px 60px 200px 1fr 80px;
align-items: center;
padding: 12px 15px;
border-radius: 8px;
margin-bottom: 4px;
cursor: pointer;
border: 1px solid transparent;
transition: var(--transition);
}
.channel-row:hover {
background: var(--focus-bg);
}
.channel-row.active {
background: var(--primary);
border-color: rgba(255,255,255,0.2);
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
transform: scale(1.01);
}
.ch-logo {
width: 40px;
height: 30px;
background: white;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #333;
font-size: 0.8rem;
}
.program-bar-container {
height: 4px;
background: rgba(255,255,255,0.15);
border-radius: 2px;
margin-top: 6px;
overflow: hidden;
}
.program-bar-fill {
height: 100%;
background: var(--text-muted);
width: 0%;
}
.channel-row.active .program-bar-fill {
background: white;
}
/* Info Panel (Bottom) */
.info-panel {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 30px 40px;
background: linear-gradient(to top, #000 10%, rgba(0,0,0,0.8) 60%, transparent 100%);
display: flex;
align-items: flex-end;
justify-content: space-between;
}
.info-content h1 {
font-size: 2.5rem;
margin-bottom: 10px;
font-weight: 700;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
.info-meta {
display: flex;
gap: 15px;
margin-bottom: 15px;
}
.tag {
background: rgba(255,255,255,0.15);
padding: 4px 10px;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
backdrop-filter: blur(4px);
}
.tag.hd { background: #f59e0b; color: #000; }
.description {
max-width: 600px;
color: var(--text-muted);
line-height: 1.5;
font-size: 1rem;
text-shadow: 0 1px 2px rgba(0,0,0,0.8);
}
/* Utils */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
.loader {
border: 3px solid rgba(255,255,255,0.1);
border-top: 3px solid var(--primary);
border-radius: 50%;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
display: none;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.hidden { display: none !important; }
</style>
</head>
<body>
<!-- Video Layer -->
<div id="video-layer">
<video id="main-player" loop muted playsinline poster="https://images.unsplash.com/photo-1593784991095-a205069470b6?q=80&w=2070&auto=format&fit=crop">
<!-- Placeholder video -->
<source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4">
</video>
</div>
<!-- Login / Connection Modal -->
<div id="login-modal">
<div class="login-container">
<div class="login-header">
<div class="brand" style="font-size: 1.2rem;">
<i class="fa-solid fa-tv"></i> <span>TiviStream Pro</span>
</div>
<div style="color: var(--text-muted); font-size: 0.9rem;">Connection Setup</div>
</div>
<div class="login-tabs">
<button class="tab-btn active" onclick="switchTab('m3u')">M3U Playlist</button>
<button class="tab-btn" onclick="switchTab('xtream')">Xtream Codes</button>
<button class="tab-btn" onclick="switchTab('mac')">Stalker MAC</button>
</div>
<div class="login-body">
<!-- M3U Form -->
<div id="form-m3u" class="auth-form">
<div class="input-group">
<label>Playlist Name</label>
<input type="text" class="form-input" placeholder="My TV List" value="Demo Playlist">
</div>
<div class="input-group">
<label>M3U URL</label>
<input type="text" class="form-input" placeholder="http://example.com/playlist.m3u" value="https://iptv-org.github.io/iptv/index.m3u">
</div>
</div>
<!-- Xtream Form -->
<div id="form-xtream" class="auth-form hidden">
<div class="input-group">
<label>Server URL</label>
<input type="text" class="form-input" placeholder="http://server-url.com:8080">
</div>
<div style="display: flex; gap: 15px;">
<div class="input-group" style="flex:1">
<label>Username</label>
<input type="text" class="form-input" placeholder="user">
</div>
<div class="input-group" style="flex:1">
<label>Password</label>
<input type="password" class="form-input" placeholder="pass">
</div>
</div>
</div>
<!-- MAC Form -->
<div id="form-mac" class="auth-form hidden">
<div class="input-group">
<label>Portal URL</label>
<input type="text" class="form-input" placeholder="http://mag.portal.com">
</div>
<div class="input-group">
<label>MAC Address</label>
<input type="text" class="form-input" placeholder="00:1A:79:XX:XX:XX" value="00:1A:79:00:00:01">
</div>
</div>
<button class="btn-primary" onclick="connectService()">
<span class="loader" id="btn-loader"></span>
<span id="btn-text">Connect Service</span>
</button>
<div style="margin-top: 15px; font-size: 0.8rem; color: var(--text-muted); text-align: center;">
<i class="fa-solid fa-lock"></i> Secure Connection •
<span style="color: var(--primary); cursor: pointer;" onclick="loadDemo()">Load Demo Mode</span>
</div>
</div>
</div>
</div>
<!-- Main UI Layer -->
<div id="ui-layer" class="hidden">
<header>
<div class="brand">
<i class="fa-solid fa-tv" style="color: var(--primary)"></i>
<span>TiviStream Pro</span>
</div>
<div class="top-meta">
<div class="connection-badge" id="conn-status">
<i class="fa-solid fa-circle" style="font-size: 6px;"></i> Live
</div>
<div id="clock">12:00 PM</div>
<i class="fa-solid fa-gear" style="cursor: pointer;" onclick="showSettings()"></i>
</div>
</header>
<div class="content-area">
<!-- Groups Sidebar -->
<div class="sidebar" id="group-list">
<!-- Groups injected via JS -->
</div>
<!-- EPG List -->
<div class="epg-container">
<div style="padding: 10px 15px; color: var(--text-muted); font-size: 0.85rem; border-bottom: 1px solid rgba(255,255,255,0.05); margin-bottom: 10px; display: flex; justify-content: space-between;">
<span>CHANNEL LIST</span>
<span id="date-display">Mon, Jan 1</span>
</div>
<div class="channel-list" id="channel-list">
<!-- Channels injected via JS -->
</div>
</div>
</div>
<!-- Bottom Info Panel -->
<div class="info-panel">
<div class="info-content">
<div class="info-meta">
<span class="tag hd">HD</span>
<span class="tag">LIVE</span>
<span class="tag" id="cat-tag">Sports</span>
</div>
<h1 id="info-title">Select Channel</h1>
<div style="color:white; font-weight:600; margin-bottom:8px; font-size: 1.1rem;">
<span id="prog-title">Current Program</span>
<span style="color: var(--text-muted); font-weight: 400; font-size: 0.9rem; margin-left: 10px;" id="prog-time">12:00 - 13:00</span>
</div>
<p class="description" id="info-desc">
Welcome to TiviStream Pro. Select a connection method to start, or use Demo Mode to preview the interface.
</p>
</div>
</div>
</div>
<script>
// --- Data Simulation ---
const categories = ['All Channels', 'Favorites', 'Sports', 'Movies', 'News', 'Kids', 'Documentary', 'Music', 'UHD 4K'];
const channelDB = [
{ id: 1, num: 101, name: "Sky Sports Main", cat: "Sports", icon: "fa-futbol", prog: "Live: Man City vs Liverpool", desc: "Exclusive live coverage of the Premier League match." },
{ id: 2, num: 102, name: "ESPN", cat: "Sports", icon: "fa-basketball", prog: "NBA: Lakers at Warriors", desc: "Live basketball action from the Crypto.com Arena." },
{ id: 3, num: 103, name: "HBO", cat: "Movies", icon: "fa-film", prog: "Dune: Part Two", desc: "Paul Atreides unites with Chani and the Fremen while on a warpath of revenge." },
{ id: 4, num: 104, name: "CNN International", cat: "News", icon: "fa-newspaper", prog: "Global News Hour", desc: "Breaking news and analysis from around the world." },
{ id: 5, num: 105, name: "Nat Geo Wild", cat: "Documentary", icon: "fa-leaf", prog: "Savage Kingdom", desc: "The struggle for survival in the African savanna." },
{ id: 6, num: 106, name: "Disney Channel", cat: "Kids", icon: "fa-shapes", prog: "Bluey", desc: "Bluey and Bingo play a game of magic statues." },
{ id: 7, num: 107, name: "MTV Live", cat: "Music", icon: "fa-music", prog: "Top 20 Hits", desc: "Countdown of the hottest tracks right now." },
{ id: 8, num: 108, name: "BBC One", cat: "News", icon: "fa-tv", prog: "BBC News at Six", desc: "The latest national and international news stories." },
{ id: 9, num: 109, name: "Cinema Action", cat: "Movies", icon: "fa-video", prog: "John Wick 4", desc: "John Wick uncovers a path to defeating The High Table." },
{ id: 10, num: 110, name: "Discovery Science", cat: "Documentary", icon: "fa-flask", prog: "How It's Made", desc: "Exploring how everyday objects are manufactured." }
];
// Generate more dummy channels
for(let i=11; i<=50; i++) {
channelDB.push({
id: i,
num: 100 + i,
name: `Channel ${100+i}`,
cat: categories[Math.floor(Math.random() * (categories.length - 2)) + 2],
icon: "fa-tv",
prog: "Regular Programming",
desc: "High definition broadcast stream."
});
}
// --- State ---
let activeCategory = 'All Channels';
let activeChannelId = null;
let connectionType = 'm3u'; // m3u, xtream, mac
// --- DOM Elements ---
const modal = document.getElementById('login-modal');
const uiLayer = document.getElementById('ui-layer');
const videoEl = document.getElementById('main-player');
// --- Functions ---
function switchTab(type) {
connectionType = type;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
event.target.classList.add('active');
document.querySelectorAll('.auth-form').forEach(f => f.classList.add('hidden'));
document.getElementById(`form-${type}`).classList.remove('hidden');
}
function connectService() {
const btnText = document.getElementById('btn-text');
const loader = document.getElementById('btn-loader');
btnText.innerText = "Authenticating...";
loader.style.display = "inline-block";
// Simulate API Latency
setTimeout(() => {
loadInterface();
}, 1500);
}
function loadDemo() {
loadInterface();
}
function loadInterface() {
modal.classList.add('hidden');
uiLayer.classList.remove('hidden');
// Init UI
renderGroups();
renderChannels('All Channels');
startClock();
// Select first channel automatically
selectChannel(channelDB[0]);
// Update connection badge
const statusText = connectionType