bep40 commited on
Commit
c96c5cb
·
verified ·
1 Parent(s): 0e76e7d

Upload static/yt_live.js

Browse files
Files changed (1) hide show
  1. static/yt_live.js +129 -28
static/yt_live.js CHANGED
@@ -1,40 +1,48 @@
1
- // === VNEWS — YouTube Live pinned to TOP of home page ===
2
- // Embeds a live YouTube stream as the very first block of #view-home.
3
  // Loaded AFTER app_v2.js so it can wrap the global loadHome().
 
4
  (function(){
5
- const LIVE_ID = 'r9NDoNv2a-Y'; // youtube.com/live/r9NDoNv2a-Y
6
- const LIVE_TITLE = 'Trực tiếp';
 
 
 
 
 
 
 
7
 
8
  // Inject styles once.
9
- if(!document.getElementById('yt-live-css')){
10
- const s=document.createElement('style');s.id='yt-live-css';
11
  s.textContent=`
12
- .yt-live-wrap{margin:6px 4px;background:#111;border:1px solid #3a0d0d;border-radius:10px;overflow:hidden;box-shadow:0 0 0 1px rgba(231,76,60,.25)}
13
- .yt-live-head{display:flex;align-items:center;gap:8px;padding:8px 10px;background:linear-gradient(90deg,#2a0808,#1a1a1a)}
14
- .yt-live-dot{width:9px;height:9px;border-radius:50%;background:#e74c3c;box-shadow:0 0 6px #e74c3c;animation:yt-live-pulse 1.3s infinite}
15
- @keyframes yt-live-pulse{0%,100%{opacity:1}50%{opacity:.3}}
16
- .yt-live-badge{font-size:11px;font-weight:800;color:#e74c3c;letter-spacing:.5px}
17
- .yt-live-title{font-size:12px;color:#eee;font-weight:700;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
18
- .yt-live-frame{position:relative;width:100%;aspect-ratio:16/9;background:#000}
19
- .yt-live-frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
 
20
  `;
21
  document.head.appendChild(s);
22
  }
23
 
24
  function buildLive(){
25
  const wrap=document.createElement('div');
26
- wrap.className='yt-live-wrap';
27
- wrap.id='yt-live-block';
28
- const src='https://www.youtube.com/embed/'+LIVE_ID
29
- +'?autoplay=1&mute=1&playsinline=1&rel=0&modestbranding=1';
30
  wrap.innerHTML=
31
- '<div class="yt-live-head">'
32
- +'<span class="yt-live-dot"></span>'
33
- +'<span class="yt-live-badge">● LIVE</span>'
34
- +'<span class="yt-live-title">'+LIVE_TITLE+'</span>'
35
  +'</div>'
36
- +'<div class="yt-live-frame">'
37
- +'<iframe src="'+src+'" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen></iframe>'
 
38
  +'</div>';
39
  return wrap;
40
  }
@@ -43,21 +51,114 @@
43
  const home=document.getElementById('view-home');
44
  if(!home)return;
45
  // Already present? do nothing (avoid reloading the stream).
46
- if(document.getElementById('yt-live-block'))return;
47
  home.insertBefore(buildLive(), home.firstChild);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
  // Wrap the existing global loadHome so the live block is (re)pinned after each render.
51
  function install(){
52
  if(typeof window.loadHome!=='function'){return false;}
53
- if(window.loadHome.__ytLiveWrapped)return true;
54
  const orig=window.loadHome;
55
  const wrapped=async function(){
56
  const r=await orig.apply(this,arguments);
57
  try{pinLive();}catch(e){}
58
  return r;
59
  };
60
- wrapped.__ytLiveWrapped=true;
61
  window.loadHome=wrapped;
62
  return true;
63
  }
@@ -70,4 +171,4 @@
70
  // In case the homepage was already rendered before this script ran, pin immediately too.
71
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',()=>setTimeout(pinLive,300));
72
  else setTimeout(pinLive,300);
73
- })();
 
1
+ // === VNEWS — VTV3 World Cup 2026 LIVE pinned to TOP of home page ===
2
+ // Replaces YouTube iframe with direct HLS stream from VTVGo using hls.js
3
  // Loaded AFTER app_v2.js so it can wrap the global loadHome().
4
+
5
  (function(){
6
+ // Common VTV3 HLS stream URLs (VTVGo package API patterns)
7
+ const VTV3_STREAMS = [
8
+ 'https://package.vtvgo.vn/stream/vtv3.m3u8',
9
+ 'https://package.vtvgo.vn/channel/vtv3.m3u8',
10
+ 'https://vtvgo.vn/stream/vtv3.m3u8',
11
+ 'https://cdn.vtvgo.vn/vtv3/playlist.m3u8',
12
+ ];
13
+
14
+ const LIVE_TITLE = 'VTV3 World Cup 2026 - Trực tiếp';
15
 
16
  // Inject styles once.
17
+ if(!document.getElementById('vtv3-live-css')){
18
+ const s=document.createElement('style');s.id='vtv3-live-css';
19
  s.textContent=`
20
+ .vtv3-live-wrap{margin:6px 4px;background:#111;border:1px solid #0066cc;border-radius:10px;overflow:hidden;box-shadow:0 0 0 1px rgba(0,102,204,.3)}
21
+ .vtv3-live-head{display:flex;align-items:center;gap:8px;padding:8px 10px;background:linear-gradient(90deg,#003366,#1a1a1a)}
22
+ .vtv3-live-dot{width:9px;height:9px;border-radius:50%;background:#00ccff;box-shadow:0 0 6px #00ccff;animation:vtv3-live-pulse 1.3s infinite}
23
+ @keyframes vtv3-live-pulse{0%,100%{opacity:1}50%{opacity:.3}}
24
+ .vtv3-live-badge{font-size:11px;font-weight:800;color:#00ccff;letter-spacing:.5px}
25
+ .vtv3-live-title{font-size:12px;color:#eee;font-weight:700;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
26
+ .vtv3-live-frame{position:relative;width:100%;aspect-ratio:16/9;background:#000}
27
+ .vtv3-live-frame video{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
28
+ .vtv3-live-error{display:flex;align-items:center;justify-content:center;height:100%;color:#888;font-size:12px;text-align:center;padding:20px}
29
  `;
30
  document.head.appendChild(s);
31
  }
32
 
33
  function buildLive(){
34
  const wrap=document.createElement('div');
35
+ wrap.className='vtv3-live-wrap';
36
+ wrap.id='vtv3-live-block';
 
 
37
  wrap.innerHTML=
38
+ '<div class="vtv3-live-head">'
39
+ +'<span class="vtv3-live-dot"></span>'
40
+ +'<span class="vtv3-live-badge">● LIVE</span>'
41
+ +'<span class="vtv3-live-title">'+LIVE_TITLE+'</span>'
42
  +'</div>'
43
+ +'<div class="vtv3-live-frame">'
44
+ +'<video id="vtv3-player" playsinline muted controls preload="auto"></video>'
45
+ +'<div class="vtv3-live-error" id="vtv3-error" style="display:none">Đang kết nối VTV3...</div>'
46
  +'</div>';
47
  return wrap;
48
  }
 
51
  const home=document.getElementById('view-home');
52
  if(!home)return;
53
  // Already present? do nothing (avoid reloading the stream).
54
+ if(document.getElementById('vtv3-live-block'))return;
55
  home.insertBefore(buildLive(), home.firstChild);
56
+ initPlayer();
57
+ }
58
+
59
+ let _hlsInstance = null;
60
+ let _currentStreamIndex = 0;
61
+ let _retryTimeout = null;
62
+
63
+ function initPlayer(){
64
+ const video = document.getElementById('vtv3-player');
65
+ const errorEl = document.getElementById('vtv3-error');
66
+ if(!video) return;
67
+
68
+ // Clean up previous HLS instance
69
+ if(_hlsInstance){
70
+ _hlsInstance.destroy();
71
+ _hlsInstance = null;
72
+ }
73
+ if(_retryTimeout){
74
+ clearTimeout(_retryTimeout);
75
+ _retryTimeout = null;
76
+ }
77
+
78
+ // Try streams in order
79
+ function tryStream(index){
80
+ if(index >= VTV3_STREAMS.length){
81
+ errorEl.textContent = 'Không thể tải luồng VTV3. Vui lòng thử lại sau.';
82
+ errorEl.style.display = 'flex';
83
+ video.style.display = 'none';
84
+ // Retry all streams after 30 seconds
85
+ _retryTimeout = setTimeout(() => {
86
+ _currentStreamIndex = 0;
87
+ tryStream(0);
88
+ }, 30000);
89
+ return;
90
+ }
91
+
92
+ const src = VTV3_STREAMS[index];
93
+ _currentStreamIndex = index;
94
+ errorEl.textContent = `Đang kết nối VTV3 (luồng ${index + 1}/${VTV3_STREAMS.length})...`;
95
+ errorEl.style.display = 'flex';
96
+ video.style.display = 'none';
97
+
98
+ if(typeof Hls !== 'undefined' && Hls.isSupported()){
99
+ const hls = new Hls({
100
+ enableWorker: true,
101
+ lowLatencyMode: true,
102
+ startLevel: -1,
103
+ capLevelToPlayerSize: true,
104
+ maxBufferLength: 30,
105
+ maxMaxBufferLength: 60,
106
+ });
107
+ _hlsInstance = hls;
108
+ hls.loadSource(src);
109
+ hls.attachMedia(video);
110
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
111
+ video.play().catch(() => {});
112
+ errorEl.style.display = 'none';
113
+ video.style.display = 'block';
114
+ });
115
+ hls.on(Hls.Events.ERROR, (event, data) => {
116
+ if(data.fatal){
117
+ switch(data.type){
118
+ case Hls.ErrorTypes.NETWORK_ERROR:
119
+ hls.startLoad();
120
+ break;
121
+ case Hls.ErrorTypes.MEDIA_ERROR:
122
+ hls.recoverMediaError();
123
+ break;
124
+ default:
125
+ hls.destroy();
126
+ _hlsInstance = null;
127
+ tryStream(index + 1);
128
+ break;
129
+ }
130
+ }
131
+ });
132
+ } else if(video.canPlayType('application/vnd.apple.mpegurl')){
133
+ // Native HLS support (Safari)
134
+ video.src = src;
135
+ video.addEventListener('loadedmetadata', () => {
136
+ video.play().catch(() => {});
137
+ errorEl.style.display = 'none';
138
+ video.style.display = 'block';
139
+ }, {once: true});
140
+ video.addEventListener('error', () => {
141
+ tryStream(index + 1);
142
+ }, {once: true});
143
+ } else {
144
+ tryStream(index + 1);
145
+ }
146
+ }
147
+
148
+ tryStream(0);
149
  }
150
 
151
  // Wrap the existing global loadHome so the live block is (re)pinned after each render.
152
  function install(){
153
  if(typeof window.loadHome!=='function'){return false;}
154
+ if(window.loadHome.__vtv3LiveWrapped)return true;
155
  const orig=window.loadHome;
156
  const wrapped=async function(){
157
  const r=await orig.apply(this,arguments);
158
  try{pinLive();}catch(e){}
159
  return r;
160
  };
161
+ wrapped.__vtv3LiveWrapped=true;
162
  window.loadHome=wrapped;
163
  return true;
164
  }
 
171
  // In case the homepage was already rendered before this script ran, pin immediately too.
172
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',()=>setTimeout(pinLive,300));
173
  else setTimeout(pinLive,300);
174
+ })();