{"id":6646,"date":"2025-08-01T00:03:33","date_gmt":"2025-07-31T17:03:33","guid":{"rendered":"https:\/\/tokuteigino.vn\/?page_id=6646"},"modified":"2026-01-29T11:20:31","modified_gmt":"2026-01-29T04:20:31","slug":"live-hoc-tu-vung-cbtp-gino1","status":"publish","type":"page","link":"https:\/\/tokuteigino.vn\/vi\/tokuteigino-1\/thuc-pham-gino-1\/live-hoc-tu-vung-cbtp-gino1\/","title":{"rendered":"Live h\u1ecdc t\u1eeb v\u1ef1ng th\u1ef1c ph\u1ea9m gino1"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"vi\">\n<head>\n  <meta charset=\"UTF-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\/>\n  <title>Kh\u00f3a h\u1ecdc Video<\/title>\n  <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@300;400;600&#038;display=swap\" rel=\"stylesheet\">\n  <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/4.7.0\/css\/font-awesome.min.css\"\/>\n\n  <style>\n\/* ========== SCOPED ONLY TO #ltt-app ========== *\/\n#ltt-app{\n  --brand:#0f2819;\n  --brand-2:#800000;\n  --muted:#f3f3f3;\n  --card:#0b1e16;\n  font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;\n  color:#111;\n  overflow-x:hidden;\n}\n#ltt-app *{ box-sizing:border-box; }\n\n\/* ================== LAYOUT: VIDEO + SIDEBAR ================== *\/\n#ltt-app .video-container-wrapper{\n  display:flex;\n  min-height:100vh;\n  background:var(--muted);\n}\n#ltt-app .video-content{\n  flex:3;\n  display:flex;\n  flex-direction:column;\n  gap:16px;\n  padding:16px;\n  background:#fff;\n}\n\n\/* ===== Section wrapper (video wrapper) ===== *\/\n#ltt-app .section{\n  position: relative;\n  overflow: visible;\n  background:#fff;\n  border:1px solid #eee;\n  border-radius:14px;\n  padding:16px;\n  box-shadow:0 2px 10px rgba(0,0,0,.06);\n}\n\n\/* ================== VIDEO PLAYER ================== *\/\n#ltt-app .video-frame{\n  display:block;\n  width:100%;\n  height:62vh; min-height:320px;\n  border:none; border-radius:12px;\n  background:#000;\n  box-shadow:0 2px 18px rgba(0,0,0,.12);\n}\n#ltt-app #ytContainer{\n  display:none;\n  width:100%;\n  height:62vh; min-height:320px;\n  border-radius:12px; overflow:hidden;\n}\n#ltt-app #ytPlayer{ width:100%; height:100%; }\n\n#ltt-app .video-navigation{\n  position: relative;\n  display:flex;\n  justify-content:center;\n  gap:14px;\n  margin:12px auto 0;\n  padding:0 12px;\n}\n#ltt-app .video-nav-button{\n  padding:5px 12px;\n  border:2px solid var(--brand);\n  border-radius:999px;\n  font-size:16px;\n  cursor:pointer;\n  transition:.2s;\n  background:#fff; color:var(--brand);\n  white-space:nowrap;\n}\n#ltt-app .video-nav-button:hover{ transform:translateY(-1px); }\n#ltt-app .video-next{ background:var(--brand); color:#fff; }\n\n\/* ================== SIDEBAR DANH S\u00c1CH B\u00c0I ================== *\/\n#ltt-app .video-course-list{\n  flex:1.1; background:#fff; padding:16px;\n  overflow-y:auto; border-left:3px solid #eee;\n}\n#ltt-app .video-course-list h3{ margin:0 0 12px; font-size:18px; color:#004080; }\n#ltt-app .video-course-item{\n  position:relative;\n  padding:12px 14px; margin-bottom:8px;\n  background:#fafafa; border:1px solid #eee; border-radius:10px;\n  cursor:pointer; color:#333; transition:.15s;\n}\n#ltt-app .video-course-item:hover{ background:#e9f6ff; transform:translateY(-1px); }\n#ltt-app .video-course-item.active{\n  background:var(--brand); color:#fff; font-weight:600;\n  border-left:5px solid #ffcc00; box-shadow:0 4px 10px rgba(0,64,128,.2);\n}\n#ltt-app .video-course-item.completed::after{\n  content:\"\u2714\"; color:#4CAF50; font-size:16px;\n  position:absolute; right:10px; top:50%; transform:translateY(-50%);\n}\n\n\/* === Desktop: sidebar fits viewport & scrolls if long === *\/\n@media (min-width: 1025px){\n  #ltt-app .video-course-list{\n    position: sticky;\n    top: 0;\n    height: 100vh;\n    max-height: 100vh;\n    overflow-y: auto;\n    overscroll-behavior: contain;\n  }\n}\n\n\/* ================== MOBILE \/ TABLET ================== *\/\n#ltt-app .mobile-menu-button{ display:none; }\n#ltt-app .mobile-course-list{ display:none; }\n\n\/* Tablet *\/\n@media (max-width: 1024px){\n  #ltt-app .video-container-wrapper{ flex-direction: column; }\n  #ltt-app .video-content{ order: 1; }\n  #ltt-app .video-course-list{ display: none !important; }\n\n  #ltt-app .mobile-menu-button{\n    display:block; background:var(--brand);\n    color:#fff; padding:12px; text-align:left; cursor:pointer;\n  }\n  #ltt-app .mobile-course-list{\n    display:none; width:100%; background:#f8f8f8;\n    box-shadow:0 4px 6px rgba(0,0,0,.1);\n    max-height:300px; overflow-y:auto;\n  }\n  #ltt-app .mobile-course-list.show{ display:block; }\n\n  #ltt-app .video-frame, \n  #ltt-app #ytContainer{ height:32vh; min-height:220px; }\n}\n\n\/* Phone: full-bleed (tr\u00e0n m\u00e0n h\u00ecnh) cho video *\/\n@media (max-width:768px){\n  #ltt-app .video-content{ padding:0; gap:15; }\n  #ltt-app .section{ border:none; border-radius:0; padding:0; margin:0; }\n\n  #ltt-app .video-frame, \n  #ltt-app #ytContainer{\n    width:100vw;\n    margin-left:calc(50% - 50vw);\n    margin-right:calc(50% - 50vw);\n    height:48vh; min-height:260px;\n    border-radius:0;\n  }\n\n  #ltt-app .video-navigation{\n    margin:10px 0 0;\n    padding-left: max(12px, env(safe-area-inset-left));\n    padding-right:max(12px, env(safe-area-inset-right));\n    gap:10px;\n  }\n}\n  <\/style>\n<\/head>\n<body>\n\n  <!-- App ch\u1ec9 g\u1ed3m VIDEO + danh s\u00e1ch b\u00e0i -->\n  <div id=\"ltt-app\">\n    <div class=\"mobile-menu-button\" onclick=\"toggleMobileMenu()\">\u2630 Danh s\u00e1ch b\u00e0i gi\u1ea3ng<\/div>\n    <div class=\"mobile-course-list\" id=\"mobileCourseList\"><\/div>\n\n    <div class=\"video-container-wrapper\">\n      <div class=\"video-content\">\n          <!-- Video -->\n          <video id=\"html5Video\" class=\"video-frame\" controls controlsList=\"nodownload\" playsinline><\/video>\n          <div id=\"ytContainer\" class=\"video-frame\"><div id=\"ytPlayer\"><\/div><\/div>\n\n          <!-- Prev\/Next -->\n          <div class=\"video-navigation\">\n            <button class=\"video-nav-button\" onclick=\"prevVideo()\">\u276e B\u00e0i tr\u01b0\u1edbc<\/button>\n            <button class=\"video-nav-button video-next\" onclick=\"nextVideo()\">B\u00e0i ti\u1ebfp theo \u276f<\/button>\n          <\/div>\n      <\/div>\n\n      <aside class=\"video-course-list\">\n        <h3>\ud83d\udcd6 Danh s\u00e1ch b\u00e0i gi\u1ea3ng<\/h3>\n        <div id=\"desktopCourseList\"><\/div>\n      <\/aside>\n    <\/div>\n  <\/div><!-- \/#ltt-app -->\n\n  <script>\n    \/* ================= C\u1ea4U H\u00ccNH ================= *\/\n    const VIDEO_SCRIPT_URL = 'https:\/\/script.google.com\/macros\/s\/AKfycbzk1dwpZFkQi1hGnhXQbi8GuKqOeBSZ_Qt1UgqeHglozqs-Kkl1EFGIrnv5on2j4O9OvA\/exec';\n    \/\/ \u0110i\u1ec1u ki\u1ec7n l\u1ecdc\n    const FILTER_CLASS_ID = 'Live t\u1eeb v\u1ef1ng cbtp gino1';\n    const FILTER_COURSE_ID = 'K07';\n    const FILTER_classSession = 'Full'; \/\/ <-- th\u00eam filter ca h\u1ecdc\n\n    \/* ================ NG\u0102N COPY\/INSPECT ================ *\/\n    document.addEventListener('contextmenu', e => e.preventDefault());\n    document.addEventListener('dragstart', e => e.preventDefault());\n    document.addEventListener('keydown', e => { if (e.ctrlKey && ['A','C','V','X'].includes(e.key.toUpperCase())) e.preventDefault(); if (e.keyCode === 123) e.preventDefault(); });\n\n    \/* ================ VIDEO STATE ================ *\/\n    let videoData=[], videoUrls=[], titles=[];\n    let currentVideoIndex=0;\n\n    const html5Video=document.getElementById('html5Video');\n    const ytContainer=document.getElementById('ytContainer');\n\n    let ytPlayer=null, ytReady=false, ytCheckInterval=null, pendingYTVideoId=null;\n\n    function isYouTubeId(s){ return typeof s==='string' && !\/^https?:\\\/\\\/\/i.test(s) && \/^[A-Za-z0-9_-]{6,15}$\/.test(s); }\n    function isYouTubeUrl(url){ try{const u=new URL(url); return u.hostname.includes('youtube.com')||u.hostname.includes('youtu.be');}catch{return false;} }\n    function getYouTubeId(url){\n      try{const u=new URL(url);\n        if(u.hostname.includes('youtu.be')) return u.pathname.slice(1);\n        if(u.hostname.includes('youtube.com')){\n          if(u.pathname==='\\\/watch') return u.searchParams.get('v');\n          if(u.pathname.startsWith('\\\/embed\\\/')) return u.pathname.split('\\\/embed\\\/')[1];\n          return u.searchParams.get('v');\n        }}catch{}\n      const m=String(url).match(\/(?:v=|\\\/embed\\\/|youtu\\.be\\\/)([A-Za-z0-9_-]{6,})\/); return m?m[1]:null;\n    }\n\n    function ensureYTPlayer(videoId){\n      ytContainer.style.display='block';\n      html5Video.style.display='none';\n      stopHtml5Monitoring();\n      if(!ytReady){ pendingYTVideoId=videoId; return; }\n      if(!ytPlayer){\n        ytPlayer=new YT.Player('ytPlayer',{\n          videoId,\n          playerVars:{rel:0,modestbranding:1,controls:1,fs:1},\n          events:{\n            onReady:(e)=>{try{e.target.playVideo();}catch{} startYTMonitoring();},\n            onStateChange:(e)=>{ if(e.data===YT.PlayerState.PLAYING) startYTMonitoring(); if(e.data===YT.PlayerState.ENDED) markCompletedIfNeeded(); }\n          }\n        });\n      }else{\n        ytPlayer.loadVideoById(videoId); try{ytPlayer.playVideo();}catch{} startYTMonitoring();\n      }\n    }\n    function startYTMonitoring(){\n      clearInterval(ytCheckInterval);\n      ytCheckInterval=setInterval(()=>{\n        if(!ytPlayer||typeof ytPlayer.getDuration!=='function') return;\n        const dur=ytPlayer.getDuration()||0, cur=ytPlayer.getCurrentTime?ytPlayer.getCurrentTime():0;\n        if(dur>0 && cur\/dur>=0.95){ markVideoAsCompleted(currentVideoIndex); localStorage.setItem(`livetuvungcbtpgino1Completed_${currentVideoIndex}`,\"true\"); clearInterval(ytCheckInterval); }\n      },1000);\n    }\n    function stopYTMonitoring(){ clearInterval(ytCheckInterval); ytCheckInterval=null; }\n    function stopYouTube(){ if(ytPlayer&&typeof ytPlayer.stopVideo==='function'){ try{ytPlayer.stopVideo();}catch{} } stopYTMonitoring(); }\n\n    function checkVideoCompletionHTML5(){\n      const cur=html5Video.currentTime||0, dur=html5Video.duration||0;\n      if(dur>0 && cur\/dur>=0.95){ markVideoAsCompleted(currentVideoIndex); localStorage.setItem(`livetuvungcbtpgino1Completed_${currentVideoIndex}`,\"true\"); }\n    }\n    function startHtml5Monitoring(){ html5Video.addEventListener('timeupdate', checkVideoCompletionHTML5); }\n    function stopHtml5Monitoring(){ html5Video.removeEventListener('timeupdate', checkVideoCompletionHTML5); }\n\n    function markVideoAsCompleted(index){\n      document.querySelectorAll(\".video-course-item\").forEach(item=>{ if(parseInt(item.getAttribute(\"data-index\"),10)===index) item.classList.add(\"completed\"); });\n    }\n    function markCompletedIfNeeded(){\n      markVideoAsCompleted(currentVideoIndex);\n      localStorage.setItem(`livetuvungcbtpgino1Completed_${currentVideoIndex}`,\"true\");\n    }\n    function setActiveItem(index){\n      document.querySelectorAll(\".video-course-item\").forEach(item=>item.classList.remove(\"active\"));\n      document.querySelectorAll(\".video-course-item\").forEach(item=>{ if(parseInt(item.getAttribute(\"data-index\"),10)===index) item.classList.add(\"active\"); });\n    }\n\n    function changeVideo(index){\n      if(!videoUrls.length || index<0 || index>=videoUrls.length) return;\n      const raw=videoUrls[index];\n      setActiveItem(index);\n\n      html5Video.pause(); stopYouTube(); html5Video.style.display='none'; ytContainer.style.display='none';\n\n      if(isYouTubeId(raw)||isYouTubeUrl(raw)){\n        const vid=isYouTubeId(raw)?raw:getYouTubeId(raw); if(!vid){ console.warn('Kh\u00f4ng l\u1ea5y \u0111\u01b0\u1ee3c videoId:',raw); return; }\n        ensureYTPlayer(vid);\n      }else{\n        html5Video.src=raw; html5Video.style.display='block'; ytContainer.style.display='none';\n        html5Video.load(); startHtml5Monitoring(); const p=html5Video.play(); if(p && typeof p.catch==='function') p.catch(()=>{});\n      }\n\n      currentVideoIndex=index; localStorage.setItem('lastlivetuvungcbtpgino1Index', currentVideoIndex);\n      if(localStorage.getItem(`livetuvungcbtpgino1Completed_${currentVideoIndex}`)===\"true\") markVideoAsCompleted(currentVideoIndex);\n    }\n    function prevVideo(){ if(currentVideoIndex>0) changeVideo(currentVideoIndex-1); }\n    function nextVideo(){ if(currentVideoIndex<videoUrls.length-1) changeVideo(currentVideoIndex+1); }\n    function toggleMobileMenu(){ document.getElementById(\"mobileCourseList\").classList.toggle(\"show\"); }\n\n    function renderCourseLists(){\n      const d=document.getElementById('desktopCourseList'), m=document.getElementById('mobileCourseList');\n      d.innerHTML=''; m.innerHTML='';\n      titles.forEach((title,idx)=>{\n        const el=document.createElement('div'); el.className='video-course-item'; el.dataset.index=idx; el.textContent=title; el.addEventListener('click',()=>changeVideo(idx)); d.appendChild(el);\n        const el2=el.cloneNode(true); el2.addEventListener('click',()=>{ changeVideo(idx); if(window.innerWidth<=1024) toggleMobileMenu(); }); m.appendChild(el2);\n      });\n    }\n\n    \/\/ === L\u1eccC D\u1eee LI\u1ec6U: classId, courseId, classSession v\u00e0 c\u00f3 videoUrl ===\n    function filterByClassCourseSession(list, classId, courseId, classSession){\n      return (Array.isArray(list)?list:[]).filter(v=>{\n        const cls = String(v && v.classId || '').trim();\n        const crs = String(v && v.courseId || '').trim();\n        const ses = String(v && v.classSession || '').trim();\n        const url = String(v && v.videoUrl || '').trim();\n        return url && cls === classId && crs === courseId && ses === classSession;\n      });\n    }\n\n    async function loadVideosFromSheet(){\n      try{\n        const res=await fetch(`${VIDEO_SCRIPT_URL}?action=fetchVideos`);\n        const data=await res.json();\n        const raw = Array.isArray(data && data.videos) ? data.videos : [];\n\n        \/\/ L\u1eccC: ch\u1ec9 l\u1ea5y \u0111\u00fang classId, courseId, v\u00e0 CA H\u1eccC\n        videoData = filterByClassCourseSession(raw, FILTER_CLASS_ID, FILTER_COURSE_ID, FILTER_classSession);\n\n        \/\/ Map sang m\u1ea3ng d\u00f9ng cho UI\n        videoUrls = videoData.map(v=> String(v.videoUrl||'')); \n        titles    = videoData.map((v,i)=> (String(v.title||'').trim()) || `Bu\u1ed5i ${i+1}`);\n\n        renderCourseLists();\n\n        if(videoUrls.length===0){\n          alert('Kh\u00f4ng c\u00f3 video n\u00e0o hi\u1ec3n th\u1ecb cho ca h\u1ecdc: ' + FILTER_classSession);\n          return;\n        }\n\n        videoUrls.forEach((_,i)=>{ if(localStorage.getItem(`livetuvungcbtpgino1Completed_${i}`)===\"true\") markVideoAsCompleted(i); });\n        const saved=localStorage.getItem('lastlivetuvungcbtpgino1Index'); const parsed = saved!==null ? parseInt(saved,10) : 0;\n        const start=(Number.isFinite(parsed) && parsed>=0 && parsed<videoUrls.length)?parsed:0;\n        changeVideo(start);\n      }catch(e){ console.error('L\u1ed7i t\u1ea3i video:',e); alert('Kh\u00f4ng t\u1ea3i \u0111\u01b0\u1ee3c danh s\u00e1ch video.'); }\n    }\n\n    html5Video.addEventListener('ended',()=>{ markVideoAsCompleted(currentVideoIndex); localStorage.setItem(`livetuvungcbtpgino1Completed_${currentVideoIndex}`,\"true\"); });\n    window.onYouTubeIframeAPIReady=function(){ ytReady=true; if(pendingYTVideoId){ const vid=pendingYTVideoId; pendingYTVideoId=null; ensureYTPlayer(vid); } };\n\n    \/* ================ TESTS (smoke) ================ *\/\n    (function devSmokeTests(){\n      try{\n        \/\/ filter tests\n        const __sample = [\n          {classId:'CBTP Gino1', courseId:'K06', classSession:'ca t\u1ed1i T2-4-6', videoUrl:'a.mp4'},\n          {classId:'CBTP Gino1', courseId:'K06', classSession:'ca s\u00e1ng T3-5-7', videoUrl:'b.mp4'},\n          {classId:'CBTP Gino1', courseId:'K07', classSession:'ca t\u1ed1i T2-4-6', videoUrl:'c.mp4'},\n          {classId:'CBTP Gino1', courseId:'K06', classSession:'ca t\u1ed1i T2-4-6', videoUrl:''},\n        ];\n        const __filtered = filterByClassCourseSession(__sample, FILTER_CLASS_ID, FILTER_COURSE_ID, FILTER_classSession);\n        console.assert(__filtered.length===1, 'Filter should keep only classId=CBTP Gino1 & courseId=K06 & classSession=ca t\u1ed1i T2-4-6 & non-empty videoUrl');\n\n        console.assert(typeof changeVideo === 'function', 'changeVideo should exist');\n        console.assert(typeof prevVideo === 'function', 'prevVideo should exist');\n        console.assert(typeof nextVideo === 'function', 'nextVideo should exist');\n        console.assert(typeof markVideoAsCompleted === 'function', 'markVideoAsCompleted should exist');\n      }catch(err){ console.warn('Smoke tests warning:', err); }\n    })();\n\n    \/* Expose handlers for inline onclick *\/\n    window.prevVideo = prevVideo;\n    window.nextVideo = nextVideo;\n    window.toggleMobileMenu = toggleMobileMenu;\n\n    \/* ================ INIT ================ *\/\n    window.addEventListener('load', ()=>{ loadVideosFromSheet(); });\n  <\/script>\n\n  <!-- YouTube IFrame API -->\n  <script src=\"https:\/\/www.youtube.com\/iframe_api\"><\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>Kh\u00f3a h\u1ecdc Video \u2630 Danh s\u00e1ch b\u00e0i gi\u1ea3ng \u276e B\u00e0i tr\u01b0\u1edbc B\u00e0i ti\u1ebfp theo \u276f \ud83d\udcd6 Danh s\u00e1ch b\u00e0i gi\u1ea3ng<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":168,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"class_list":["post-6646","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/pages\/6646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/comments?post=6646"}],"version-history":[{"count":5,"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/pages\/6646\/revisions"}],"predecessor-version":[{"id":7858,"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/pages\/6646\/revisions\/7858"}],"up":[{"embeddable":true,"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/pages\/168"}],"wp:attachment":[{"href":"https:\/\/tokuteigino.vn\/vi\/wp-json\/wp\/v2\/media?parent=6646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}