clarkeplay / index.html
AptlyDigital's picture
Update index.html
bc95524 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clarke Player Pro • 4K IPTV Player</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
/* Clarke Player Pro - Complete CSS */
:root {
--primary: #8B5CF6;
--primary-dark: #7C3AED;
--primary-light: #A78BFA;
--accent: #10B981;
--accent-dark: #059669;
--danger: #EF4444;
--warning: #F59E0B;
--info: #3B82F6;
--bg-dark: #0F172A;
--bg-card: #1E293B;
--bg-panel: #334155;
--bg-surface: rgba(255, 255, 255, 0.05);
--text-primary: #F1F5F9;
--text-secondary: #94A3B8;
--text-muted: #64748B;
--border: rgba(255, 255, 255, 0.1);
--border-light: rgba(255, 255, 255, 0.05);
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--gradient-primary: linear-gradient(135deg, var(--primary), var(--primary-dark));
--gradient-accent: linear-gradient(135deg, var(--accent), var(--accent-dark));
--gradient-dark: linear-gradient(135deg, var(--bg-dark), #1A1F35);
--glass-bg: rgba(255, 255, 255, 0.025);
--glass-border: rgba(255, 255, 255, 0.1);
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg-dark);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
font-weight: 400;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
position: relative;
overflow: hidden;
}
/* Animated Background */
.bg-animation {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
overflow: hidden;
}
.gradient-circle {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.15;
animation: float 20s infinite ease-in-out;
}
.gradient-circle:nth-child(1) {
width: 600px;
height: 600px;
background: var(--primary);
top: -300px;
left: -300px;
animation-delay: 0s;
}
.gradient-circle:nth-child(2) {
width: 500px;
height: 500px;
background: var(--accent);
bottom: -250px;
right: -250px;
animation-delay: 5s;
}
.gradient-circle:nth-child(3) {
width: 400px;
height: 400px;
background: var(--info);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation-delay: 10s;
}
@keyframes float {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-20px) scale(1.05); }
}
/* Main Header */
.main-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background: var(--glass-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--glass-border);
z-index: 100;
box-shadow: var(--glass-shadow);
}
.header-left {
display: flex;
align-items: center;
gap: 2rem;
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
}
.logo i {
font-size: 2rem;
color: var(--primary-light);
background: var(--gradient-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.logo h1 {
font-size: 1.75rem;
font-weight: 700;
background: linear-gradient(135deg, var(--text-primary), var(--primary-light));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.logo h1 span {
color: var(--primary);
}
.logo sup {
font-size: 0.7rem;
background: var(--gradient-accent);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-left: 0.25rem;
font-weight: 600;
}
.player-status {
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.05);
padding: 0.5rem 1rem;
border-radius: 20px;
border: 1px solid var(--border-light);
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.1); }
100% { opacity: 1; transform: scale(1); }
}
.header-right {
display: flex;
align-items: center;
gap: 1rem;
}
.ui-btn {
width: 40px;
height: 40px;
border-radius: 12px;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.ui-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
transform: translateY(-2px);
box-shadow: var(--shadow);
}
.user-profile .avatar {
width: 40px;
height: 40px;
border-radius: 12px;
background: var(--gradient-primary);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.9rem;
color: white;
cursor: pointer;
transition: var(--transition);
}
.user-profile .avatar:hover {
transform: scale(1.05);
box-shadow: var(--shadow);
}
/* Main Content */
.main-content {
display: grid;
grid-template-columns: 320px 1fr 320px;
gap: 1.5rem;
padding: 1.5rem;
flex: 1;
overflow: hidden;
}
@media (max-width: 1920px) {
.main-content {
grid-template-columns: 300px 1fr 300px;
}
}
/* Panel Cards */
.panel-card {
background: var(--glass-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: var(--glass-shadow);
transition: var(--transition);
}
.panel-card:hover {
border-color: rgba(255, 255, 255, 0.15);
}
.panel-card h3 {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.panel-card h3 i {
color: var(--primary-light);
}
/* Left Panel - Playlist Input */
.input-group {
margin-bottom: 1.5rem;
}
.input-group label {
display: block;
font-size: 0.875rem;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.input-with-icon {
position: relative;
}
.input-with-icon i {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
}
.input-with-icon input {
width: 100%;
padding: 0.875rem 1rem 0.875rem 2.75rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text-primary);
font-size: 0.875rem;
transition: var(--transition);
}
.input-with-icon input:focus {
outline: none;
border-color: var(--primary);
background: rgba(255, 255, 255, 0.08);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
}
.file-upload-area {
border: 2px dashed var(--border);
border-radius: 12px;
padding: 2rem 1rem;
text-align: center;
cursor: pointer;
transition: var(--transition);
background: rgba(255, 255, 255, 0.02);
}
.file-upload-area:hover {
border-color: var(--primary);
background: rgba(255, 255, 255, 0.05);
}
.file-upload-area i {
font-size: 2rem;
color: var(--text-muted);
margin-bottom: 0.75rem;
}
.file-upload-area p {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0;
}
.quick-presets {
margin-bottom: 1.5rem;
}
.quick-presets h4 {
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 0.75rem;
}
.preset-buttons {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.preset-btn {
padding: 0.75rem 1rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text-secondary);
font-size: 0.875rem;
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
transition: var(--transition);
}
.preset-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: var(--primary);
color: var(--text-primary);
transform: translateX(4px);
}
.action-buttons {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.btn-primary, .btn-secondary {
flex: 1;
padding: 1rem;
border: none;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.btn-primary {
background: var(--gradient-primary);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(139, 92, 246, 0.3);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
color: var(--text-secondary);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
}
.playlist-info .info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.info-item {
text-align: center;
}
.info-item span {
display: block;
font-size: 0.75rem;
color: var(--text-secondary);
margin-bottom: 0.25rem;
}
.info-item strong {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
}
/* Center Panel - Video Player */
.center-panel {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.video-container {
position: relative;
background: #000;
border-radius: 20px;
overflow: hidden;
aspect-ratio: 16/9;
box-shadow: var(--shadow-xl);
}
#videoPlayer {
width: 100%;
height: 100%;
display: block;
outline: none;
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.7));
display: flex;
align-items: flex-end;
padding: 2rem;
opacity: 0;
transition: var(--transition);
}
.video-container:hover .video-overlay {
opacity: 1;
}
.now-playing {
display: flex;
align-items: center;
gap: 1rem;
}
.channel-logo-large {
width: 60px;
height: 60px;
border-radius: 15px;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: var(--primary-light);
}
.now-playing-info h2 {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.now-playing-info p {
color: var(--text-secondary);
font-size: 0.875rem;
}
.player-controls {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 2rem;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(20px);
padding: 1rem 2rem;
border-radius: 50px;
border: 1px solid rgba(255, 255, 255, 0.1);
opacity: 0;
transition: var(--transition);
}
.video-container:hover .player-controls {
opacity: 1;
}
.control-group {
display: flex;
gap: 1rem;
}
.control-btn {
width: 48px;
height: 48px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.1);
}
.control-btn.play-btn {
background: var(--gradient-primary);
border: none;
}
.control-btn.play-btn:hover {
background: var(--primary-dark);
box-shadow: 0 0 20px rgba(139, 92, 246, 0.5);
}
.volume-control {
display: flex;
align-items: center;
gap: 0.75rem;
}
.volume-control i {
color: var(--text-secondary);
}
.volume-control input[type="range"] {
width: 100px;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
outline: none;
-webkit-appearance: none;
}
.volume-control input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--primary);
cursor: pointer;
border: 2px solid white;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
}
.loading-overlay .spinner {
width: 60px;
height: 60px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* EPG Timeline */
.epg-timeline {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 15px;
padding: 1.25rem;
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.timeline-header h4 {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.timeline-nav {
display: flex;
gap: 0.5rem;
}
.nav-btn {
width: 32px;
height: 32px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.nav-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
}
.timeline-content {
display: flex;
gap: 1rem;
overflow-x: auto;
padding-bottom: 0.5rem;
}
.timeline-content::-webkit-scrollbar {
height: 4px;
}
.timeline-content::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 2px;
}
.timeline-content::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 2px;
}
.epg-item {
flex-shrink: 0;
padding: 0.75rem 1rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
border: 1px solid transparent;
transition: var(--transition);
min-width: 180px;
}
.epg-item.active {
background: var(--gradient-primary);
border-color: var(--primary);
}
.epg-time {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 0.25rem;
}
.epg-item.active .epg-time {
color: rgba(255, 255, 255, 0.9);
}
.epg-show {
font-size: 0.875rem;
color: var(--text-primary);
}
.epg-item.active .epg-show {
color: white;
font-weight: 500;
}
/* Channels Grid */
.channels-grid {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: 20px;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
}
.grid-header {
padding: 1.5rem;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.grid-header h3 {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.grid-controls {
display: flex;
align-items: center;
gap: 1rem;
}
.search-box {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 12px;
padding: 0.5rem 0.75rem;
gap: 0.5rem;
}
.search-box i {
color: var(--text-muted);
font-size: 0.875rem;
}
.search-box input {
background: transparent;
border: none;
color: var(--text-primary);
font-size: 0.875rem;
width: 200px;
outline: none;
}
.search-box input::placeholder {
color: var(--text-muted);
}
.view-toggle {
display: flex;
gap: 0.25rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 12px;
padding: 0.25rem;
}
.view-btn {
width: 32px;
height: 32px;
border-radius: 8px;
background: transparent;
border: none;
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.view-btn.active {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary);
}
.view-btn:hover:not(.active) {
background: rgba(255, 255, 255, 0.05);
}
.channels-container {
flex: 1;
padding: 1.5rem;
overflow-y: auto;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
align-content: start;
}
.channels-container::-webkit-scrollbar {
width: 8px;
}
.channels-container::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
.channels-container::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 4px;
}
.channels-container::-webkit-scrollbar-thumb:hover {
background: var(--primary-dark);
}
.channel-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border);
border-radius: 15px;
padding: 1.25rem;
cursor: pointer;
transition: var(--transition);
position: relative;
overflow: hidden;
}
.channel-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: var(--gradient-primary);
transform: scaleX(0);
transition: var(--transition);
}
.channel-card:hover {
background: rgba(255, 255, 255, 0.06);
border-color: var(--primary);
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.channel-card:hover::before {
transform: scaleX(1);
}
.channel-card.active {
background: rgba(139, 92, 246, 0.1);
border-color: var(--primary);
}
.channel-card.active::before {
transform: scaleX(1);
}
.channel-logo {
width: 48px;
height: 48px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
font-size: 1.25rem;
color: var(--primary-light);
}
.channel-info h4 {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.channel-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.75rem;
color: var(--text-secondary);
}
.channel-number {
font-weight: 600;
color: var(--primary-light);
}
.channel-fav {
position: absolute;
top: 1rem;
right: 1rem;
color: var(--text-muted);
cursor: pointer;
transition: var(--transition);
}
.channel-fav:hover {
color: #FFD700;
transform: scale(1.2);
}
.channel-fav.active {
color: #FFD700;
}
.welcome-state {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
text-align: center;
color: var(--text-secondary);
}
.welcome-state i {
font-size: 4rem;
margin-bottom: 1.5rem;
color: var(--primary-light);
opacity: 0.5;
}
.welcome-state h3 {
font-size: 1.5rem;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
/* Right Panel */
.current-channel-info {
margin-bottom: 1.5rem;
}
.channel-display {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.channel-logo {
width: 48px;
height: 48px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: var(--primary-light);
}
.channel-details {
flex: 1;
}
.channel-details h4 {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.25rem;
}
.channel-region {
font-size: 0.75rem;
color: var(--text-secondary);
background: rgba(255, 255, 255, 0.05);
padding: 0.25rem 0.5rem;
border-radius: 6px;
display: inline-block;
}
.fav-btn {
width: 40px;
height: 40px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
color: var(--text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.fav-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: #FFD700;
}
.fav-btn.active {
color: #FFD700;
border-color: rgba(255, 215, 0, 0.3);
}
.quality-indicator {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 10px;
border: 1px solid var(--border);
}
.quality-badge {
padding: 0.25rem 0.75rem;
background: var(--gradient-accent);
color: white;
font-size: 0.75rem;
font-weight: 600;
border-radius: 20px;
}
.resolution {
font-size: 0.75rem;
color: var(--text-secondary);
}
.program-info {
margin-bottom: 1.5rem;
}
.program-info h4 {
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 0.75rem;
}
.program-details p {
font-size: 0.875rem;
color: var(--text-primary);
margin-bottom: 0.75rem;
line-height: 1.5;
}
.program-time {
display: flex;
align-items: center;
gap: 0.75rem;
}
.time-badge {
padding: 0.25rem 0.5rem;
background: var(--danger);
color: white;
font-size: 0.75rem;
font-weight: 600;
border-radius: 6px;
}
.duration {
font-size: 0.75rem;
color: var(--text-secondary);
}
.audio-tracks h4 {
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 0.75rem;
}
.track-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.track-item {
padding: 0.75rem;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border);
border-radius: 10px;
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.875rem;
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
}
.track-item:hover {
background: rgba(255, 255, 255, 0.06);
border-color: var(--primary);
}
.track-item.active {
background: rgba(139, 92, 246, 0.1);
border-color: var(--primary);
color: var(--text-primary);
}
.filter-group {
display: flex;
flex-direction: column;
gap: 1rem;
}
.filter-item label {
display: block;
font-size: 0.875rem;
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.filter-item select {
width: 100%;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text-primary);
font-size: 0.875rem;
cursor: pointer;
transition: var(--transition);
}
.filter-item select:focus {
outline: none;
border-color: var(--primary);
background: rgba(255, 255, 255, 0.08);
}
.quality-filter {
display: flex;
gap: 1rem;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
}
.checkbox-label input[type="checkbox"] {
width: 16px;
height: 16px;
border-radius: 4px;
border: 1px solid var(--border);
background: rgba(255, 255, 255, 0.05);
cursor: pointer;
transition: var(--transition);
}
.checkbox-label input[type="checkbox"]:checked {
background: var(--primary);
border-color: var(--primary);
}
.checkbox-label:hover {
color: var(--text-primary);
}
.sort-buttons {
display: flex;
gap: 0.5rem;
}
.sort-btn {
flex: 1;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-secondary);
font-size: 0.75rem;
cursor: pointer;
transition: var(--transition);
}
.sort-btn.active {
background: rgba(139, 92, 246, 0.1);
border-color: var(--primary);
color: var(--text-primary);
}
.sort-btn:hover:not(.active) {
background: rgba(255, 255, 255, 0.08);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.stat-item {
text-align: center;
padding: 1rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 12px;
border: 1px solid var(--border);
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: 10px;
background: rgba(139, 92, 246, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 0.75rem;
color: var(--primary-light);
font-size: 1.25rem;
}
.stat-info span {
display: block;
font-size: 0.75rem;
color: var(--text-secondary);
margin-bottom: 0.25rem;
}
.stat-info strong {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
}
/* Bottom Bar */
.bottom-bar {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border-top: 1px solid var(--glass-border);
padding: 0.75rem 2rem;
box-shadow: var(--glass-shadow);
}
.progress-bar {
width: 100%;
height: 3px;
background: rgba(255, 255, 255, 0.05);
border-radius: 2px;
margin-bottom: 0.75rem;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--gradient-primary);
width: 0%;
transition: width 0.3s ease;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.playback-info {
font-size: 0.875rem;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 0.25rem;
}
.separator {
color: var(--text-muted);
}
.shortcuts-info {
display: flex;
gap: 1.5rem;
}
.shortcut-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.shortcut-item kbd {
padding: 0.25rem 0.5rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid var(--border);
border-radius: 6px;
font-family: 'Inter', monospace;
font-size: 0.75rem;
color: var(--text-primary);
min-width: 36px;
text-align: center;
}
.shortcut-item span {
font-size: 0.75rem;
color: var(--text-secondary);
}
.version-info {
font-size: 0.75rem;
color: var(--text-muted);
opacity: 0.7;
}
/* Favorites Section */
.favorites-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
max-height: 200px;
overflow-y: auto;
}
.favorites-list::-webkit-scrollbar {
width: 4px;
}
.favorites-list::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
}
.favorites-list::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 2px;
}
.favorite-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border);
border-radius: 10px;
cursor: pointer;
transition: var(--transition);
}
.favorite-item:hover {
background: rgba(255, 255, 255, 0.06);
border-color: var(--primary);
}
.favorite-item i {
color: #FFD700;
font-size: 0.875rem;
}
.favorite-item span {
font-size: 0.875rem;
color: var(--text-primary);
flex: 1;
}
.empty-favorites {
text-align: center;
padding: 2rem 1rem;
color: var(--text-secondary);
}
.empty-favorites i {
font-size: 2rem;
margin-bottom: 0.75rem;
opacity: 0.5;
}
.empty-favorites p {
font-size: 0.875rem;
}
/* Responsive */
@media (max-width: 1600px) {
.main-content {
grid-template-columns: 280px 1fr 280px;
}
}
@media (max-width: 1366px) {
.main-content {
grid-template-columns: 260px 1fr 260px;
}
}
@media (max-width: 1200px) {
.main-content {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr auto;
}
.left-panel, .right-panel {
display: none;
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- Animated Background -->
<div class="bg-animation">
<div class="gradient-circle"></div>
<div class="gradient-circle"></div>
<div class="gradient-circle"></div>
</div>
<!-- Main Header -->
<header class="main-header">
<div class="header-left">
<div class="logo">
<i class="fas fa-broadcast-tower"></i>
<h1>Clarke<span>Player</span><sup>PRO</sup></h1>
</div>
<div class="player-status">
<div class="status-indicator"></div>
<span id="statusText">Ready</span>
</div>
</div>
<div class="header-right">
<button class="ui-btn" id="settingsBtn" title="Settings">
<i class="fas fa-sliders-h"></i>
</button>
<button class="ui-btn" id="fullscreenBtn" title="Fullscreen">
<i class="fas fa-expand"></i>
</button>
<div class="user-profile">
<div class="avatar">CP</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="main-content">
<!-- Left Panel - Playlist Input & Controls -->
<aside class="left-panel">
<div class="panel-card">
<h3><i class="fas fa-cloud-upload-alt"></i> Load Playlist</h3>
<div class="input-group">
<label>Playlist URL</label>
<div class="input-with-icon">
<i class="fas fa-link"></i>
<input type="text" id="playlistUrl"
placeholder="https://example.com/playlist.m3u8"
value="https://i.mjh.nz/PlutoTV/us.m3u8">
</div>
</div>
<div class="input-group">
<label>Or upload file</label>
<div class="file-upload-area" id="uploadArea">
<i class="fas fa-file-upload"></i>
<p>Drop .m3u/.m3u8 file here or click to browse</p>
<input type="file" id="fileInput" accept=".m3u,.m3u8" hidden>
</div>
</div>
<div class="quick-presets">
<h4>Quick Presets</h4>
<div class="preset-buttons">
<button class="preset-btn" data-url="https://i.mjh.nz/PlutoTV/us.m3u8">
<i class="fas fa-flag-usa"></i> PlutoTV US
</button>
<button class="preset-btn" data-url="https://i.mjh.nz/PlutoTV/gb.m3u8">
<i class="fas fa-flag-uk"></i> PlutoTV UK
</button>
<button class="preset-btn" data-url="https://iptv-org.github.io/iptv/index.m3u">
<i class="fas fa-globe"></i> Global IPTV
</button>
</div>
</div>
<div class="action-buttons">
<button class="btn-primary" id="loadPlaylistBtn">
<i class="fas fa-play-circle"></i> Load & Play
</button>
<button class="btn-secondary" id="clearBtn">
<i class="fas fa-trash"></i> Clear
</button>
</div>
<div class="playlist-info" id="playlistInfo">
<h4>Playlist Info</h4>
<div class="info-grid">
<div class="info-item">
<span>Channels</span>
<strong id="channelCount">0</strong>
</div>
<div class="info-item">
<span>Last Updated</span>
<strong id="lastUpdate">--</strong>
</div>
<div class="info-item">
<span>Quality</span>
<strong id="avgQuality">Auto</strong>
</div>
</div>
</div>
</div>
<!-- Favorites Section -->
<div class="panel-card favorites-section">
<h3><i class="fas fa-star"></i> Favorites</h3>
<div class="favorites-list" id="favoritesList">
<div class="empty-favorites">
<i class="fas fa-star"></i>
<p>No favorites yet</p>
</div>
</div>
</div>
</aside>
<!-- Center Panel - Video Player -->
<main class="center-panel">
<div class="video-container" id="videoContainer">
<div class="video-overlay">
<div class="now-playing">
<div class="channel-logo-large">
<i class="fas fa-satellite"></i>
</div>
<div class="now-playing-info">
<h2 id="currentChannel">Welcome to Clarke Player Pro</h2>
<p id="currentProgram">Load a playlist to start streaming</p>
</div>
</div>
</div>
<video id="videoPlayer" controls playsinline></video>
<div class="player-controls">
<div class="control-group">
<button class="control-btn" id="prevChannel">
<i class="fas fa-step-backward"></i>
</button>
<button class="control-btn play-btn" id="playPauseBtn">
<i class="fas fa-play"></i>
</button>
<button class="control-btn" id="nextChannel">
<i class="fas fa-step-forward"></i>
</button>
</div>
<div class="volume-control">
<i class="fas fa-volume-up"></i>
<input type="range" id="volumeSlider" min="0" max="100" value="80">
</div>
</div>
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
<p>Loading stream...</p>
</div>
</div>
<!-- EPG Timeline -->
<div class="epg-timeline">
<div class="timeline-header">
<h4><i class="fas fa-tv"></i> What's On Now</h4>
<div class="timeline-nav">
<button class="nav-btn"><i class="fas fa-chevron-left"></i></button>
<button class="nav-btn"><i class="fas fa-chevron-right"></i></button>
</div>
</div>
<div class="timeline-content" id="epgTimeline">
<div class="epg-item active">
<div class="epg-time">NOW</div>
<div class="epg-show">Select a channel</div>
</div>
</div>
</div>
<!-- Channel Grid -->
<div class="channels-grid">
<div class="grid-header">
<h3><i class="fas fa-th-large"></i> Channels</h3>
<div class="grid-controls">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="channelSearch" placeholder="Search channels...">
</div>
<div class="view-toggle">
<button class="view-btn active"><i class="fas fa-th-large"></i></button>
<button class="view-btn"><i class="fas fa-list"></i></button>
</div>
</div>
</div>
<div class="channels-container" id="channelsContainer">
<!-- Channels will be loaded here -->
<div class="welcome-state">
<i class="fas fa-broadcast-tower"></i>
<h3>Load Your Playlist</h3>
<p>Enter a playlist URL above or upload a file to get started</p>
</div>
</div>
</div>
</main>
<!-- Right Panel - Channel Guide -->
<aside class="right-panel">
<div class="panel-card">
<h3><i class="fas fa-tv"></i> Now Playing</h3>
<div class="current-channel-info">
<div class="channel-display">
<div class="channel-logo">
<i class="fas fa-satellite"></i>
</div>
<div class="channel-details">
<h4 id="nowPlayingChannel">--</h4>
<p class="channel-region" id="nowPlayingRegion">--</p>
</div>
<button class="fav-btn" id="toggleFav">
<i class="far fa-star"></i>
</button>
</div>
<div class="quality-indicator">
<div class="quality-badge" id="qualityBadge">Auto</div>
<div class="resolution">4K Ready</div>
</div>
</div>
<div class="program-info">
<h4>Program Information</h4>
<div class="program-details">
<p id="programDescription">No program information available</p>
<div class="program-time">
<span class="time-badge">LIVE</span>
<span class="duration">00:00 - 00:00</span>
</div>
</div>
</div>
<div class="audio-tracks">
<h4>Audio Tracks</h4>
<div class="track-list" id="audioTracks">
<div class="track-item active">
<i class="fas fa-volume-up"></i>
<span>English</span>
</div>
</div>
</div>
</div>
<!-- Filter Section -->
<div class="panel-card">
<h3><i class="fas fa-filter"></i> Filter Channels</h3>
<div class="filter-group">
<div class="filter-item">
<label>Category</label>
<select id="categoryFilter">
<option value="all">All Categories</option>
<option value="entertainment">Entertainment</option>
<option value="news">News</option>
<option value="sports">Sports</option>
<option value="movies">Movies</option>
</select>
</div>
<div class="filter-item">
<label>Quality</label>
<div class="quality-filter">
<label class="checkbox-label">
<input type="checkbox" checked> 4K
</label>
<label class="checkbox-label">
<input type="checkbox" checked> HD
</label>
<label class="checkbox-label">
<input type="checkbox" checked> SD
</label>
</div>
</div>
<div class="filter-item">
<label>Sort By</label>
<div class="sort-buttons">
<button class="sort-btn active">Name A-Z</button>
<button class="sort-btn">Favorites</button>
<button class="sort-btn">Recent</button>
</div>
</div>
</div>
</div>
<!-- Stats Section -->
<div class="panel-card stats-card">
<h3><i class="fas fa-chart-line"></i> Player Stats</h3>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-wifi"></i>
</div>
<div class="stat-info">
<span>Bitrate</span>
<strong id="bitrateStat">0 Mbps</strong>
</div>
</div>
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-tachometer-alt"></i>
</div>
<div class="stat-info">
<span>Buffer</span>
<strong id="bufferStat">0s</strong>
</div>
</div>
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-heartbeat"></i>
</div>
<div class="stat-info">
<span>Health</span>
<strong id="healthStat">100%</strong>
</div>
</div>
</div>
</div>
</aside>
</div>
<!-- Bottom Bar -->
<footer class="bottom-bar">
<div class="progress-bar">
<div class="progress-fill" id="bufferProgress"></div>
</div>
<div class="footer-content">
<div class="playback-info">
<span id="currentTime">00:00</span>
<span class="separator">/</span>
<span id="totalTime">00:00</span>
</div>
<div class="shortcuts-info">
<div class="shortcut-item">
<kbd>SPACE</kbd>
<span>Play/Pause</span>
</div>
<div class="shortcut-item">
<kbd>F</kbd>
<span>Fullscreen</span>
</div>
<div class="shortcut-item">
<kbd>M</kbd>
<span>Mute</span>
</div>
<div class="shortcut-item">
<kbd>← →</kbd>
<span>Navigate</span>
</div>
</div>
<div class="version-info">
Clarke Player Pro v2.1 • 4K Optimized
</div>
</div>
</footer>
</div>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.4.10/dist/hls.min.js"></script>
<script>
// Clarke Player Pro - Complete JavaScript
class ClarkePlayerPro {
constructor() {
this.state = {
playlists: [],
currentPlaylist: null,
channels: [],
filteredChannels: [],
favorites: JSON.parse(localStorage.getItem('clarke_favs')) || {},
currentChannel: null,
hls: null,
isPlaying: false,
quality: 'Auto',
searchQuery: '',
filterCategory: 'all'
};
this.elements = {
video: document.getElementById('videoPlayer'),
playlistUrl: document.getElementById('playlistUrl'),
fileInput: document.getElementById('fileInput'),
uploadArea: document.getElementById('uploadArea'),
loadPlaylistBtn: document.getElementById('loadPlaylistBtn'),
clearBtn: document.getElementById('clearBtn'),
channelsContainer: document.getElementById('channelsContainer'),
currentChannel: document.getElementById('currentChannel'),
currentProgram: document.getElementById('currentProgram'),
nowPlayingChannel: document.getElementById('nowPlayingChannel'),
nowPlayingRegion: document.getElementById('nowPlayingRegion'),
statusText: document.getElementById('statusText'),
channelCount: document.getElementById('channelCount'),
lastUpdate: document.getElementById('lastUpdate'),
avgQuality: document.getElementById('avgQuality'),
loadingOverlay: document.getElementById('loadingOverlay'),
channelSearch: document.getElementById('channelSearch'),
toggleFav: document.getElementById('toggleFav'),
playPauseBtn: document.getElementById('playPauseBtn'),
prevChannel: document.getElementById('prevChannel'),
nextChannel: document.getElementById('nextChannel'),
volumeSlider: document.getElementById('volumeSlider'),
favoritesList: document.getElementById('favoritesList'),
categoryFilter: document.getElementById('categoryFilter'),
fullscreenBtn: document.getElementById('fullscreenBtn'),
settingsBtn: document.getElementById('settingsBtn'),
bitrateStat: document.getElementById('bitrateStat'),
bufferStat: document.getElementById('bufferStat'),
healthStat: document.getElementById('healthStat'),
qualityBadge: document.getElementById('qualityBadge'),
bufferProgress: document.getElementById('bufferProgress'),
currentTime: document.getElementById('currentTime'),
totalTime: document.getElementById('totalTime')
};
this.init();
}
init() {
console.log('🚀 Clarke Player Pro v2.1 Initializing...');
this.setupEventListeners();
this.setupKeyboardControls();
this.loadDefaultPlaylist();
this.updateStats();
this.renderFavorites();
// Set initial volume
this.elements.video.volume = this.elements.volumeSlider.value / 100;
}
setupEventListeners() {
// Load playlist from URL
this.elements.loadPlaylistBtn.addEventListener('click', () => {
this.loadPlaylistFromUrl();
});
// Quick preset buttons
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const url = e.currentTarget.dataset.url;
this.elements.playlistUrl.value = url;
this.loadPlaylistFromUrl();
});
});
// File upload
this.elements.uploadArea.addEventListener('click', () => {
this.elements.fileInput.click();
});
this.elements.fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
this.loadPlaylistFromFile(e.target.files[0]);
}
});
// Drag and drop for file upload
this.elements.uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
e.currentTarget.style.borderColor = 'var(--primary)';
e.currentTarget.style.background = 'rgba(139, 92, 246, 0.1)';
});
this.elements.uploadArea.addEventListener('dragleave', (e) => {
e.currentTarget.style.borderColor = 'var(--border)';
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.02)';
});
this.elements.uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
e.currentTarget.style.borderColor = 'var(--border)';
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.02)';
const file = e.dataTransfer.files[0];
if (file && (file.name.endsWith('.m3u') || file.name.endsWith('.m3u8'))) {
this.loadPlaylistFromFile(file);
} else {
this.showNotification('Please drop a valid .m3u or .m3u8 file', 'error');
}
});
// Clear button
this.elements.clearBtn.addEventListener('click', () => {
this.clearPlaylist();
});
// Search
this.elements.channelSearch.addEventListener('input', (e) => {
this.state.searchQuery = e.target.value.toLowerCase();
this.filterChannels();
});
// Category filter
this.elements.categoryFilter.addEventListener('change', (e) => {
this.state.filterCategory = e.target.value;
this.filterChannels();
});
// Player controls
this.elements.playPauseBtn.addEventListener('click', () => {
this.togglePlayPause();
});
this.elements.prevChannel.addEventListener('click', () => {
this.selectPreviousChannel();
});
this.elements.nextChannel.addEventListener('click', () => {
this.selectNextChannel();
});
this.elements.toggleFav.addEventListener('click', () => {
if (this.state.currentChannel) {
this.toggleFavorite(this.state.currentChannel.id);
}
});
// Volume control
this.elements.volumeSlider.addEventListener('input', (e) => {
this.elements.video.volume = e.target.value / 100;
});
// Fullscreen
this.elements.fullscreenBtn.addEventListener('click', () => {
this.toggleFullscreen();
});
// Video events
this.elements.video.addEventListener('play', () => {
this.state.isPlaying = true;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
this.updateStatus('Playing');
});
this.elements.video.addEventListener('pause', () => {
this.state.isPlaying = false;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
this.updateStatus('Paused');
});
this.elements.video.addEventListener('waiting', () => {
this.showLoading(true);
this.updateStatus('Buffering...');
});
this.elements.video.addEventListener('playing', () => {
this.showLoading(false);
this.updateStatus('Playing');
});
this.elements.video.addEventListener('timeupdate', () => {
this.updatePlaybackInfo();
});
this.elements.video.addEventListener('loadedmetadata', () => {
this.updatePlaybackInfo();
});
// Auto-hide controls
let controlsTimeout;
this.elements.video.addEventListener('mousemove', () => {
const overlay = document.querySelector('.video-overlay');
const controls = document.querySelector('.player-controls');
overlay.style.opacity = '1';
controls.style.opacity = '1';
clearTimeout(controlsTimeout);
controlsTimeout = setTimeout(() => {
if (!document.fullscreenElement) {
overlay.style.opacity = '0';
controls.style.opacity = '0';
}
}, 3000);
});
}
setupKeyboardControls() {
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT') return;
switch (e.key.toLowerCase()) {
case ' ':
e.preventDefault();
this.togglePlayPause();
break;
case 'arrowleft':
e.preventDefault();
this.seek(-10);
break;
case 'arrowright':
e.preventDefault();
this.seek(10);
break;
case 'arrowup':
e.preventDefault();
this.selectPreviousChannel();
break;
case 'arrowdown':
e.preventDefault();
this.selectNextChannel();
break;
case 'f':
e.preventDefault();
this.toggleFullscreen();
break;
case 'm':
e.preventDefault();
this.toggleMute();
break;
case 'l':
e.preventDefault();
this.loadPlaylistFromUrl();
break;
case 'escape':
if (document.fullscreenElement) {
document.exitFullscreen();
}
break;
}
});
}
async loadDefaultPlaylist() {
const defaultUrl = this.elements.playlistUrl.value;
if (defaultUrl) {
await this.loadPlaylistFromUrl();
}
}
async loadPlaylistFromUrl() {
const url = this.elements.playlistUrl.value.trim();
if (!url) {
this.showNotification('Please enter a playlist URL', 'error');
return;
}
this.updateStatus('Loading playlist...');
this.showLoading(true);
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const text = await response.text();
await this.parsePlaylist(text, url);
this.showNotification('Playlist loaded successfully', 'success');
this.elements.lastUpdate.textContent = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load playlist:', error);
this.showNotification('Failed to load playlist. Please check the URL.', 'error');
this.updateStatus('Load failed');
} finally {
this.showLoading(false);
}
}
async loadPlaylistFromFile(file) {
this.updateStatus('Reading playlist file...');
this.showLoading(true);
try {
const text = await file.text();
await this.parsePlaylist(text, file.name);
this.showNotification(`Loaded playlist: ${file.name}`, 'success');
this.elements.lastUpdate.textContent = new Date().toLocaleTimeString();
} catch (error) {
console.error('Failed to load file:', error);
this.showNotification('Failed to load playlist file', 'error');
} finally {
this.showLoading(false);
}
}
async parsePlaylist(content, source) {
const channels = [];
const lines = content.split('\n');
let currentChannel = {};
let channelNumber = 1;
for (let line of lines) {
line = line.trim();
if (line.startsWith('#EXTINF')) {
const titleMatch = line.match(/,(.*)$/);
const logoMatch = line.match(/tvg-logo="([^"]+)"/);
const groupMatch = line.match(/group-title="([^"]+)"/);
currentChannel = {
id: `channel_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
title: titleMatch ? this.cleanTitle(titleMatch[1]) : `Channel ${channelNumber}`,
logo: logoMatch ? logoMatch[1] : '',
group: groupMatch ? groupMatch[1] : 'General',
number: channelNumber,
source: source,
url: '',
isFavorite: false,
category: this.detectCategory(titleMatch ? titleMatch[1] : '')
};
channelNumber++;
}
else if (line.startsWith('http')) {
currentChannel.url = line;
channels.push({...currentChannel});
}
}
this.state.channels = channels;
this.state.filteredChannels = [...channels];
this.updateChannelCount();
this.renderChannels();
if (channels.length > 0) {
this.selectChannel(0);
}
}
cleanTitle(title) {
return title
.replace(/^Pluto TV\s*/i, '')
.replace(/^\[.*?\]\s*/, '')
.replace(/\|.*$/, '')
.trim();
}
detectCategory(title) {
const lowerTitle = title.toLowerCase();
if (lowerTitle.includes('news') || lowerTitle.includes('cnn') || lowerTitle.includes('bbc')) {
return 'news';
} else if (lowerTitle.includes('sport') || lowerTitle.includes('espn') || lowerTitle.includes('football')) {
return 'sports';
} else if (lowerTitle.includes('movie') || lowerTitle.includes('cinema') || lowerTitle.includes('film')) {
return 'movies';
} else if (lowerTitle.includes('music') || lowerTitle.includes('mtv') || lowerTitle.includes('vibe')) {
return 'music';
} else if (lowerTitle.includes('kids') || lowerTitle.includes('cartoon') || lowerTitle.includes('disney')) {
return 'kids';
} else {
return 'entertainment';
}
}
renderChannels() {
const container = this.elements.channelsContainer;
if (this.state.filteredChannels.length === 0) {
container.innerHTML = `
<div class="welcome-state">
<i class="fas fa-broadcast-tower"></i>
<h3>No Channels Found</h3>
<p>Try a different search term or filter</p>
</div>
`;
return;
}
let html = '';
this.state.filteredChannels.forEach((channel, index) => {
const isActive = this.state.currentChannel &&
this.state.currentChannel.id === channel.id;
const isFavorite = this.state.favorites[channel.id];
html += `
<div class="channel-card ${isActive ? 'active' : ''}"
data-index="${index}"
data-id="${channel.id}">
<div class="channel-logo">
${channel.logo ?
`<img src="${channel.logo}" alt="${channel.title}"
onerror="this.style.display='none'; this.parentElement.innerHTML='<i class=\"fas fa-tv\"></i>'">` :
`<i class="fas fa-tv"></i>`}
</div>
<div class="channel-info">
<h4>${channel.title}</h4>
<div class="channel-meta">
<span class="channel-number">${channel.number}</span>
<span class="channel-category">${channel.category}</span>
</div>
</div>
<div class="channel-fav ${isFavorite ? 'active' : ''}"
data-id="${channel.id}">
<i class="fas fa-star"></i>
</div>
</div>
`;
});
container.innerHTML = html;
// Add event listeners
container.querySelectorAll('.channel-card').forEach(card => {
card.addEventListener('click', (e) => {
if (!e.target.closest('.channel-fav')) {
const index = parseInt(card.dataset.index);
this.selectChannel(index);
}
});
});
container.querySelectorAll('.channel-fav').forEach(fav => {
fav.addEventListener('click', (e) => {
e.stopPropagation();
const channelId = fav.dataset.id;
this.toggleFavorite(channelId);
fav.classList.toggle('active');
});
});
}
filterChannels() {
let filtered = this.state.channels;
// Apply search filter
if (this.state.searchQuery) {
filtered = filtered.filter(ch =>
ch.title.toLowerCase().includes(this.state.searchQuery) ||
ch.group.toLowerCase().includes(this.state.searchQuery) ||
ch.category.toLowerCase().includes(this.state.searchQuery)
);
}
// Apply category filter
if (this.state.filterCategory !== 'all') {
filtered = filtered.filter(ch =>
ch.category === this.state.filterCategory
);
}
this.state.filteredChannels = filtered;
this.renderChannels();
this.updateChannelCount();
}
selectChannel(index) {
if (index < 0 || index >= this.state.filteredChannels.length) return;
const channel = this.state.filteredChannels[index];
this.state.currentChannel = channel;
// Update UI
this.elements.currentChannel.textContent = channel.title;
this.elements.currentProgram.textContent = `Now Playing • ${channel.group}`;
this.elements.nowPlayingChannel.textContent = channel.title;
this.elements.nowPlayingRegion.textContent = channel.group;
// Update favorite button
this.elements.toggleFav.classList.toggle('active', this.state.favorites[channel.id]);
// Update active state in list
document.querySelectorAll('.channel-card').forEach(card => {
card.classList.remove('active');
});
const selectedCard = document.querySelector(`.channel-card[data-index="${index}"]`);
if (selectedCard) {
selectedCard.classList.add('active');
selectedCard.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// Play the channel
this.playChannel(channel.url);
this.updateStatus(`Playing: ${channel.title}`);
}
selectNextChannel() {
if (!this.state.currentChannel || this.state.filteredChannels.length === 0) return;
const currentIndex = this.state.filteredChannels.findIndex(
ch => ch.id === this.state.currentChannel.id
);
if (currentIndex >= 0) {
const nextIndex = (currentIndex + 1) % this.state.filteredChannels.length;
this.selectChannel(nextIndex);
} else if (this.state.filteredChannels.length > 0) {
this.selectChannel(0);
}
}
selectPreviousChannel() {
if (!this.state.currentChannel || this.state.filteredChannels.length === 0) return;
const currentIndex = this.state.filteredChannels.findIndex(
ch => ch.id === this.state.currentChannel.id
);
if (currentIndex >= 0) {
const prevIndex = currentIndex - 1 >= 0 ?
currentIndex - 1 :
this.state.filteredChannels.length - 1;
this.selectChannel(prevIndex);
} else if (this.state.filteredChannels.length > 0) {
this.selectChannel(0);
}
}
playChannel(url) {
this.showLoading(true);
// Destroy previous HLS instance
if (this.state.hls) {
this.state.hls.destroy();
this.state.hls = null;
}
// Stop current video
this.elements.video.pause();
this.elements.video.src = '';
if (Hls.isSupported()) {
this.state.hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 30,
maxBufferLength: 60,
debug: false,
liveSyncDurationCount: 3,
liveMaxLatencyDurationCount: 10
});
this.state.hls.loadSource(url);
this.state.hls.attachMedia(this.elements.video);
this.state.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.elements.video.play().then(() => {
this.showLoading(false);
this.state.isPlaying = true;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
}).catch(error => {
console.log('Autoplay prevented:', error);
this.showLoading(false);
this.updateStatus('Click play button to start');
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
});
});
this.state.hls.on(Hls.Events.ERROR, (event, data) => {
console.error('HLS Error:', data);
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
this.updateStatus('Network error - retrying...');
this.state.hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
this.updateStatus('Media error - recovering...');
this.state.hls.recoverMediaError();
break;
default:
this.state.hls.destroy();
this.showNotification('Playback failed. Trying next channel...', 'error');
this.selectNextChannel();
break;
}
}
});
this.state.hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
const level = this.state.hls.levels[data.level];
if (level) {
const bitrate = Math.round(level.bitrate / 1000);
const height = level.height || 'Auto';
this.state.quality = height === 'Auto' ? 'Auto' : `${height}p`;
this.elements.qualityBadge.textContent = this.state.quality;
this.elements.bitrateStat.textContent = `${bitrate} Mbps`;
this.updateQualityStats();
}
});
this.state.hls.on(Hls.Events.BUFFER_CREATED, () => {
this.updateBufferStats();
});
} else if (this.elements.video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari native HLS support
this.elements.video.src = url;
this.elements.video.play().then(() => {
this.showLoading(false);
this.state.isPlaying = true;
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
}).catch(error => {
console.log('Native HLS autoplay prevented:', error);
this.showLoading(false);
this.updateStatus('Click play button to start');
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
});
} else {
this.showLoading(false);
this.showNotification('Your browser does not support HLS streaming', 'error');
}
}
togglePlayPause() {
if (this.elements.video.paused) {
this.elements.video.play();
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
} else {
this.elements.video.pause();
this.elements.playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
}
}
toggleFavorite(channelId) {
if (this.state.favorites[channelId]) {
delete this.state.favorites[channelId];
this.showNotification('Removed from favorites', 'info');
} else {
this.state.favorites[channelId] = true;
this.showNotification('Added to favorites', 'success');
}
localStorage.setItem('clarke_favs', JSON.stringify(this.state.favorites));
this.renderFavorites();
// Update favorite button if this is the current channel
if (this.state.currentChannel && this.state.currentChannel.id === channelId) {
this.elements.toggleFav.classList.toggle('active', this.state.favorites[channelId]);
}
}
renderFavorites() {
const container = this.elements.favoritesList;
const favoriteChannels = this.state.channels.filter(ch => this.state.favorites[ch.id]);
if (favoriteChannels.length === 0) {
container.innerHTML = `
<div class="empty-favorites">
<i class="fas fa-star"></i>
<p>No favorites yet</p>
</div>
`;
return;
}
let html = '';
favoriteChannels.forEach(channel => {
html += `
<div class="favorite-item" data-id="${channel.id}">
<i class="fas fa-star"></i>
<span>${channel.title}</span>
</div>
`;
});
container.innerHTML = html;
// Add click listeners
container.querySelectorAll('.favorite-item').forEach(item => {
item.addEventListener('click', () => {
const channelId = item.dataset.id;
const channelIndex = this.state.filteredChannels.findIndex(ch => ch.id === channelId);
if (channelIndex >= 0) {
this.selectChannel(channelIndex);
}
});
});
}
toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.log(`Fullscreen error: ${err.message}`);
});
} else {
document.exitFullscreen();
}
}
toggleMute() {
this.elements.video.muted = !this.elements.video.muted;
this.updateStatus(this.elements.video.muted ? 'Muted' : 'Unmuted');
}
seek(seconds) {
this.elements.video.currentTime += seconds;
}
updateStatus(text) {
this.elements.statusText.textContent = text;
}
updateChannelCount() {
const count = this.state.filteredChannels.length;
const total = this.state.channels.length;
this.elements.channelCount.textContent = `${count}`;
// Update average quality estimation
if (total > 0) {
const hdCount = this.state.channels.filter(ch =>
ch.title.toLowerCase().includes('hd') ||
ch.title.toLowerCase().includes('1080') ||
ch.title.toLowerCase().includes('4k')
).length;
const hdPercentage = Math.round((hdCount / total) * 100);
this.elements.avgQuality.textContent = `${hdPercentage}% HD`;
}
}
updatePlaybackInfo() {
const current = this.elements.video.currentTime;
const duration = this.elements.video.duration || 0;
// Format time
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
this.elements.currentTime.textContent = formatTime(current);
this.elements.totalTime.textContent = formatTime(duration);
// Update buffer progress
if (this.elements.video.buffered.length > 0) {
const bufferedEnd = this.elements.video.buffered.end(this.elements.video.buffered.length - 1);
const bufferPercentage = duration > 0 ? (bufferedEnd / duration) * 100 : 0;
this.elements.bufferProgress.style.width = `${bufferPercentage}%`;
}
}
updateStats() {
// Update health based on buffering
if (this.state.hls) {
const bufferLength = this.state.hls.media.buffered.length;
const health = bufferLength > 0 ? 100 : 80;
this.elements.healthStat.textContent = `${health}%`;
}
// Update buffer time
if (this.elements.video.buffered.length > 0) {
const bufferedEnd = this.elements.video.buffered.end(this.elements.video.buffered.length - 1);
const bufferTime = Math.round(bufferedEnd - this.elements.video.currentTime);
this.elements.bufferStat.textContent = `${bufferTime}s`;
}
// Update every second
setTimeout(() => this.updateStats(), 1000);
}
updateBufferStats() {
if (this.state.hls) {
const bufferInfo = this.state.hls.media.buffered;
if (bufferInfo.length > 0) {
const bufferTime = bufferInfo.end(bufferInfo.length - 1) - this.elements.video.currentTime;
this.elements.bufferStat.textContent = `${Math.round(bufferTime)}s`;
}
}
}
updateQualityStats() {
// Update quality distribution
if (this.state.channels.length > 0) {
const hdChannels = this.state.channels.filter(ch =>
ch.title.toLowerCase().includes('hd') ||
ch.title.toLowerCase().includes('1080') ||
ch.title.toLowerCase().includes('4k')
).length;
const hdPercentage = Math.round((hdChannels / this.state.channels.length) * 100);
this.elements.avgQuality.textContent = `${hdPercentage}% HD`;
}
}
clearPlaylist() {
this.state.channels = [];
this.state.filteredChannels = [];
this.state.currentChannel = null;
if (this.state.hls) {
this.state.hls.destroy();
this.state.hls = null;
}
this.elements.video.pause();
this.elements.video.src = '';
this.renderChannels();
this.updateChannelCount();
this.updateStatus('Playlist cleared');
this.elements.currentChannel.textContent = 'Welcome to Clarke Player Pro';
this.elements.currentProgram.textContent = 'Load a playlist to start streaming';
this.elements.nowPlayingChannel.textContent = '--';
this.elements.nowPlayingRegion.textContent = '--';
this.showNotification('Playlist cleared', 'info');
}
showLoading(show) {
this.elements.loadingOverlay.style.display = show ? 'flex' : 'none';
}
showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
<span>${message}</span>
`;
// Add styles for notification
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? 'var(--accent)' : type === 'error' ? 'var(--danger)' : 'var(--info)'};
color: white;
padding: 1rem 1.5rem;
border-radius: 12px;
display: flex;
align-items: center;
gap: 0.75rem;
z-index: 10000;
animation: slideIn 0.3s ease;
box-shadow: var(--shadow-lg);
max-width: 300px;
`;
document.body.appendChild(notification);
// Remove after 3 seconds
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 3000);
// Add animation keyframes
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
if (!document.querySelector('#notification-styles')) {
style.id = 'notification-styles';
document.head.appendChild(style);
}
}
}
// Initialize Clarke Player Pro
let player;
document.addEventListener('DOMContentLoaded', () => {
player = new ClarkePlayerPro();
});
</script>
</body>
</html>