2026-08-29 12:42:36 +03:00
( function () {
if ( window . __YAKTUBE_ENGINE_LOADED__ ) return ;
window . __YAKTUBE_ENGINE_LOADED__ = true ;
console . log ( '[YakTube] YakNet SSO, Custom Branding, A11y & On-Demand Engine Active' );
// Dynamic Instance Name Helper
function getInstanceName () {
if (
window . PeertubeServerConfig &&
window . PeertubeServerConfig . instance &&
window . PeertubeServerConfig . instance . name
) {
return window . PeertubeServerConfig . instance . name ;
}
var metaSite = document . querySelector ( 'meta[property="og:site_name"]' );
if ( metaSite && metaSite . content ) return metaSite . content ;
var brandEl = document . querySelector ( '.header-left .brand-name, .instance-name, a.brand' );
if ( brandEl && brandEl . textContent ) return brandEl . textContent . trim ();
return 'YakTube' ;
}
// 1. Screen Reader Live Announcer
var liveRegion = null ;
function getLiveRegion () {
if ( ! liveRegion || ! document . body . contains ( liveRegion )) {
liveRegion = document . getElementById ( 'yaktube-a11y-live-region' );
if ( ! liveRegion ) {
liveRegion = document . createElement ( 'div' );
liveRegion . id = 'yaktube-a11y-live-region' ;
liveRegion . setAttribute ( 'aria-live' , 'polite' );
liveRegion . setAttribute ( 'aria-atomic' , 'true' );
liveRegion . className = 'yaktube-sr-only' ;
document . body . appendChild ( liveRegion );
}
}
return liveRegion ;
}
function announce ( text , assertive ) {
var region = getLiveRegion ();
if ( assertive ) {
region . setAttribute ( 'aria-live' , 'assertive' );
} else {
region . setAttribute ( 'aria-live' , 'polite' );
}
region . textContent = '' ;
setTimeout ( function () {
region . textContent = text ;
}, 50 );
}
function triggerYakNetOAuth () {
sessionStorage . removeItem ( 'yaknet_manual_logout' );
var callbackUrl = 'https://yaktube.yakhub.com.tr/plugins/peertube-plugin-auth-yaknet/router/auth-callback' ;
window . location . href =
'https://auth.yakhub.com.tr/oauth/authorize?client_id=01a03c41-f758-721e-b927-619bffde5c23&redirect_uri=' +
encodeURIComponent ( callbackUrl ) +
'&response_type=code&scope=' ;
}
// Handle direct postMessage from YakNet SSO frame
window . addEventListener ( 'message' , function ( ev ) {
if ( ev . data && ev . data . type === 'YAKNET_SSO_STATUS' ) {
if ( ev . data . authenticated && ev . data . user ) {
var token = localStorage . getItem ( 'access_token' );
if (
! token &&
! window . location . search . includes ( 'local=true' ) &&
! window . location . search . includes ( 'externalAuthError' )
) {
sessionStorage . removeItem ( 'yaknet_manual_logout' );
console . log ( '[YakTube] Live YakNet session detected via SSO frame! Connecting...' , ev . data . user );
triggerYakNetOAuth ();
}
} else if ( ev . data . authenticated === false ) {
var hasToken = localStorage . getItem ( 'access_token' ) || localStorage . getItem ( 'username' );
if ( hasToken ) {
console . log ( '[YakTube] YakNet session ended elsewhere. Clearing YakTube...' );
try {
localStorage . clear ();
sessionStorage . clear ();
} catch ( e ) {}
window . location . reload ();
}
}
}
});
// Global listener for YakNet SSO broadcast from auth.yakhub.com.tr
window . addEventListener ( 'yaknet-auth' , function ( e ) {
if ( e . detail && e . detail . authenticated && e . detail . user ) {
var token = localStorage . getItem ( 'access_token' );
if (
! token &&
! window . location . search . includes ( 'local=true' ) &&
! window . location . search . includes ( 'externalAuthError' )
) {
sessionStorage . removeItem ( 'yaknet_manual_logout' );
console . log ( '[YakTube] Initiating automatic single-sign-on handshake for:' , e . detail . user );
triggerYakNetOAuth ();
}
} else if ( e . detail && e . detail . authenticated === false ) {
var hasToken = localStorage . getItem ( 'access_token' ) || localStorage . getItem ( 'username' );
if ( hasToken ) {
try {
localStorage . clear ();
sessionStorage . clear ();
} catch ( err ) {}
window . location . reload ();
}
}
});
// Unified single sign-out for YakTube and YakNet
function performUnifiedLogout () {
try {
localStorage . clear ();
sessionStorage . clear ();
sessionStorage . setItem ( 'yaknet_manual_logout' , 'true' );
} catch ( err ) {}
window . location . replace (
'https://auth.yakhub.com.tr/sdk/logout?redirect_uri=' + encodeURIComponent ( 'https://yaktube.yakhub.com.tr' )
);
}
document . addEventListener (
'click' ,
function ( e ) {
var el = e . target ;
if ( el && el . closest ) {
var isLogout = el . closest (
'a[href*="logout"], button[title*="Log out"], button[title*="Çıkış"], .logout-button, [class*="logout"], [aria-label*="Çıkış"], [aria-label*="Log out"]'
);
if ( isLogout ) {
e . preventDefault ();
e . stopPropagation ();
performUnifiedLogout ();
return ;
}
var isLogin = el . closest (
'my-login-link, .login-button, a[href*="/login"]:not(#yaktube-show-admin-login), a[aria-label*="Giriş"], a[title*="Giriş"], a[title*="login"], a[aria-label*="login"], .yaknet-sso-login-btn'
);
if ( isLogin && ! window . location . search . includes ( 'local=true' )) {
e . preventDefault ();
e . stopPropagation ();
sessionStorage . removeItem ( 'yaknet_manual_logout' );
triggerYakNetOAuth ();
return ;
}
}
},
true
);
2026-08-31 14:04:28 +03:00
// 2.3 Dedicated Local Admin Login Modal (Alt+A)
function openLocalAdminLoginModal () {
var existingModal = document . getElementById ( 'yaktube-admin-login-modal' );
if ( existingModal ) {
existingModal . style . display = 'flex' ;
var uInput = existingModal . querySelector ( '#yaktube-admin-username' );
if ( uInput ) uInput . focus ();
return ;
}
var modal = document . createElement ( 'div' );
modal . id = 'yaktube-admin-login-modal' ;
modal . className = 'yaktube-modal-overlay' ;
modal . setAttribute ( 'role' , 'dialog' );
modal . setAttribute ( 'aria-modal' , 'true' );
modal . setAttribute ( 'aria-labelledby' , 'yaktube-admin-modal-title' );
modal . innerHTML =
'<div class="yaktube-modal-card" style="max-width: 420px; background:#181824; border:1px solid rgba(255,143,55,0.4); border-radius:18px; padding:26px; color:#fff; box-shadow:0 16px 40px rgba(0,0,0,0.8); text-align:left;">' +
'<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:18px;">' +
'<h3 id="yaktube-admin-modal-title" style="margin:0; font-size:18px; color:#ff8f37;">⚙️ Sunucu Yöneticisi Girişi</h3>' +
'<button id="yaktube-admin-modal-close" class="yaktube-voice-modal-close-btn" aria-label="Kapat">✕</button>' +
'</div>' +
'<p style="font-size:13px; color:#aaa; margin-bottom:16px;">Yerel yönetici (admin) kullanı cı adı ve şifrenizle giriş yaparak PeerTube yönetim paneline erişebilirsiniz.</p>' +
'<form id="yaktube-admin-direct-form" style="display:flex; flex-direction:column; gap:12px;">' +
'<div style="display:flex; flex-direction:column; gap:4px;">' +
'<label for="yaktube-admin-username" style="font-size:13px; font-weight:600; color:#ddd;">Kullanı cı Adı veya E-posta:</label>' +
'<input type="text" id="yaktube-admin-username" required style="background:#22222e; border:1px solid #444; border-radius:8px; padding:10px 12px; color:#fff; font-size:14px; outline:none;" />' +
'</div>' +
'<div style="display:flex; flex-direction:column; gap:4px;">' +
'<label for="yaktube-admin-password" style="font-size:13px; font-weight:600; color:#ddd;">Şifre:</label>' +
'<input type="password" id="yaktube-admin-password" required style="background:#22222e; border:1px solid #444; border-radius:8px; padding:10px 12px; color:#fff; font-size:14px; outline:none;" />' +
'</div>' +
'<div id="yaktube-admin-login-error" style="color:#ef4444; font-size:13px; display:none; margin-top:4px;"></div>' +
'<button type="submit" id="yaktube-admin-login-submit" style="background:linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%); color:#fff; border:none; border-radius:8px; padding:12px; font-weight:700; font-size:14px; cursor:pointer; margin-top:6px;">Yönetici Olarak Giriş Yap</button>' +
'</form>' +
'</div>' ;
document . body . appendChild ( modal );
modal . querySelector ( '#yaktube-admin-modal-close' ). addEventListener ( 'click' , function () {
modal . style . display = 'none' ;
});
modal . addEventListener ( 'click' , function ( e ) {
if ( e . target === modal ) modal . style . display = 'none' ;
});
var form = modal . querySelector ( '#yaktube-admin-direct-form' );
var errEl = modal . querySelector ( '#yaktube-admin-login-error' );
var uInput = modal . querySelector ( '#yaktube-admin-username' );
var pInput = modal . querySelector ( '#yaktube-admin-password' );
var sBtn = modal . querySelector ( '#yaktube-admin-login-submit' );
uInput . focus ();
form . addEventListener ( 'submit' , async function ( ev ) {
ev . preventDefault ();
errEl . style . display = 'none' ;
sBtn . disabled = true ;
sBtn . innerText = 'Giriş Yapı lı yor...' ;
try {
var clientRes = await fetch ( '/api/v1/oauth-clients/local' );
var clientData = await clientRes . json ();
var bodyParams = new URLSearchParams ({
client_id : clientData . client_id ,
client_secret : clientData . client_secret ,
grant_type : 'password' ,
username : uInput . value . trim (),
password : pInput . value
});
var tokenRes = await fetch ( '/api/v1/users/token' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/x-www-form-urlencoded' },
body : bodyParams . toString ()
});
var tokenData = await tokenRes . json ();
if ( ! tokenRes . ok || ! tokenData . access_token ) {
throw new Error ( tokenData . error_description || tokenData . error || 'Kullanı cı adı veya şifre hatalı .' );
}
// Store tokens
localStorage . setItem ( 'access_token' , tokenData . access_token );
localStorage . setItem ( 'refresh_token' , tokenData . refresh_token );
localStorage . setItem ( 'token_type' , tokenData . token_type || 'Bearer' );
localStorage . setItem ( 'username' , uInput . value . trim ());
// Get user details
var meRes = await fetch ( '/api/v1/users/me' , {
headers : { Authorization : 'Bearer ' + tokenData . access_token }
});
if ( meRes . ok ) {
var meData = await meRes . json ();
localStorage . setItem ( 'email' , meData . email || '' );
localStorage . setItem ( 'displayName' , meData . displayName || meData . username || '' );
}
announce ( 'Yönetici girişi başarı lı ! Yönetim paneline yönlendiriliyorsunuz.' , true );
window . location . href = '/admin' ;
} catch ( err ) {
errEl . innerText = err . message ;
errEl . style . display = 'block' ;
sBtn . disabled = false ;
sBtn . innerText = 'Yönetici Olarak Giriş Yap' ;
}
});
}
2026-08-29 12:42:36 +03:00
// 2. YakNet SSO Header & Login Flow
function injectYakNetSSO () {
// 2.1 Header: Place YakNet Account widget in top-right .buttons-container
var headerRight =
document . querySelector ( 'my-header .buttons-container' ) ||
document . querySelector ( '.buttons-container' ) ||
document . querySelector ( 'my-header .header-right' ) ||
document . querySelector ( 'my-header .user-menu' ) ||
document . querySelector ( 'my-header .action-buttons' ) ||
document . querySelector ( '.header-right' ) ||
document . querySelector ( 'header .right' );
var token = localStorage . getItem ( 'access_token' );
var uName = localStorage . getItem ( 'username' );
var uEmail = localStorage . getItem ( 'email' );
var displayName = localStorage . getItem ( 'displayName' ) || uName || '' ;
var emailStr = uEmail || '' ;
var isAuth = !! token && !! uName ;
if ( headerRight ) {
2026-08-31 14:32:22 +03:00
var defaultLoginLink = headerRight . querySelector ( 'my-login-link, a[href*="/login"], .login-button' );
2026-08-29 12:42:36 +03:00
var liveWidget = headerRight . querySelector ( '#yaknet-header-account-widget' );
2026-08-31 14:32:22 +03:00
if ( isAuth ) {
// Logged In: Show YakNet Profile Menu, Hide default login link
2026-08-29 12:42:36 +03:00
if ( defaultLoginLink ) {
defaultLoginLink . style . display = 'none' ;
2026-08-31 14:32:22 +03:00
defaultLoginLink . setAttribute ( 'aria-hidden' , 'true' );
2026-08-29 12:42:36 +03:00
}
2026-08-31 14:32:22 +03:00
if ( ! liveWidget ) {
var widgetWrap = document . createElement ( 'div' );
widgetWrap . id = 'yaknet-header-account-widget' ;
widgetWrap . className = 'yaknet-header-widget' ;
widgetWrap . style . display = 'inline-flex' ;
widgetWrap . style . alignItems = 'center' ;
widgetWrap . style . marginRight = '8px' ;
widgetWrap . innerHTML =
'<yaknet-account ' +
'client-id="01a03c41-f758-721e-b927-619bffde5c23" ' +
'redirect-uri="https://yaktube.yakhub.com.tr/plugins/peertube-plugin-auth-yaknet/router/auth-callback" ' +
'base-url="https://auth.yakhub.com.tr" ' +
'login-url="/login" ' +
'authenticated="true" ' +
'user-name="' +
displayName . replace ( /"/g , '"' ) +
'" ' +
'user-email="' +
emailStr . replace ( /"/g , '"' ) +
'" ' +
'theme="dark">' +
'</yaknet-account>' ;
var targetSibling =
headerRight . querySelector ( 'my-user-menu' ) ||
headerRight . querySelector ( '.user-menu' ) ||
headerRight . querySelector ( '.settings-button' ) ||
headerRight . querySelector ( 'my-user-notifications' ) ||
headerRight . querySelector ( 'my-notification-bell' );
if ( targetSibling && targetSibling . parentElement === headerRight ) {
headerRight . insertBefore ( widgetWrap , targetSibling );
} else {
headerRight . prepend ( widgetWrap );
}
} else {
var el = liveWidget . querySelector ( 'yaknet-account' );
if ( el ) {
if ( el . shadowRoot ) {
var shadowLogoutBtn =
el . shadowRoot . getElementById ( 'logoutBtn' ) ||
el . shadowRoot . querySelector ( '.yn-logout-btn' ) ||
el . shadowRoot . querySelector ( 'button[title*="Çıkış"], button[aria-label*="Çıkış"]' );
if ( shadowLogoutBtn && ! shadowLogoutBtn . hasAttribute ( 'data-yaktube-hooked' )) {
shadowLogoutBtn . setAttribute ( 'data-yaktube-hooked' , 'true' );
shadowLogoutBtn . addEventListener (
'click' ,
function ( ev ) {
ev . preventDefault ();
ev . stopPropagation ();
performUnifiedLogout ();
},
true
);
}
}
if ( el . getAttribute ( 'authenticated' ) !== 'true' || el . getAttribute ( 'user-name' ) !== displayName ) {
el . setAttribute ( 'authenticated' , 'true' );
el . setAttribute ( 'user-name' , displayName );
el . setAttribute ( 'user-email' , emailStr );
if ( typeof el . syncStateFromAttributes === 'function' ) {
el . syncStateFromAttributes ();
el . render ();
}
2026-08-29 12:42:36 +03:00
}
}
2026-08-31 14:32:22 +03:00
}
} else {
// Not Logged In: Remove YakNet widget to eliminate duplicate button, keep PeerTube native login button clean
if ( liveWidget ) {
liveWidget . remove ();
}
if ( defaultLoginLink ) {
defaultLoginLink . style . display = '' ;
defaultLoginLink . removeAttribute ( 'aria-hidden' );
2026-08-29 12:42:36 +03:00
2026-08-31 14:32:22 +03:00
var loginAnchor =
defaultLoginLink . tagName . toLowerCase () === 'a' ? defaultLoginLink : defaultLoginLink . querySelector ( 'a' );
if ( loginAnchor ) {
loginAnchor . setAttribute ( 'aria-label' , 'YakNet ile Giriş Yap' );
loginAnchor . setAttribute ( 'title' , 'YakNet Hesabı nı z ile Giriş Yapı n' );
var spanText = loginAnchor . querySelector ( 'span' );
if ( spanText ) {
spanText . textContent = 'YakNet ile Giriş Yap' ;
} else {
loginAnchor . textContent = 'YakNet ile Giriş Yap' ;
2026-08-29 12:42:36 +03:00
}
}
}
}
}
// 2.2 Login Modal / Page
var loginCard =
document . querySelector ( 'my-login' ) ||
document . querySelector ( '.login-page' ) ||
document . querySelector ( '.login-container' ) ||
document . querySelector ( 'form.login' ) ||
document . querySelector ( 'form[action*="login"]' ) ||
document . querySelector ( 'my-login-modal' );
if ( loginCard && ! document . getElementById ( 'yaknet-login-section' )) {
var isLocalLogin = window . location . search . includes ( 'local=true' ) || window . location . pathname . includes ( '/admin' );
var ssoBox = document . createElement ( 'div' );
ssoBox . id = 'yaknet-login-section' ;
ssoBox . className = 'yaknet-login-banner' ;
ssoBox . innerHTML =
'<div style="font-size: 19px; font-weight: 700; color: #ff8f37; margin-bottom: 6px;">YakNet ile YakTube\'a Katı lı n</div>' +
'<p style="font-size: 13px; color: #94a3b8; margin-bottom: 16px; line-height: 1.45;">' +
'YouTube gibi tek bir YakNet hesabı yla güvenle giriş yapabilir, kendi video ve müzik kanalları nı zı oluşturabilirsiniz.' +
'</p>' +
'<a href="https://auth.yakhub.com.tr/oauth/authorize?client_id=01a03c41-f758-721e-b927-619bffde5c23&redirect_uri=https%3A%2F%2Fyaktube.yakhub.com.tr%2Fplugins%2Fpeertube-plugin-auth-yaknet%2Frouter%2Fauth-callback&response_type=code&scope=" class="yaknet-sso-login-btn" aria-label="YakNet Hesabı nı z ile Tek Tı kla Giriş Yapı n">' +
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/></svg>' +
'<span>YakNet ile Giriş Yap</span>' +
'</a>' +
'<div style="margin-top: 14px; font-size: 13px; color: #94a3b8;">' +
'YakNet hesabı nı z yok mu? <a href="https://auth.yakhub.com.tr/register" target="_blank" rel="noopener noreferrer" style="color: #ff8f37; font-weight: 600; text-decoration: underline;">YakNet Hesabı Oluştur</a>' +
'</div>' +
'<div style="margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.08); font-size: 11.5px; color: #64748b; line-height: 1.45;">' +
'Giriş yaparak <a href="/about/instance" target="_blank" style="color: #94a3b8; text-decoration: underline;">Kullanı m Koşulları </a>\'nı ve <a href="/about/instance" target="_blank" style="color: #94a3b8; text-decoration: underline;">Gizlilik Politikası </a>\'nı kabul etmiş sayı lı rsı nı z.' +
'</div>' +
'<div class="yaktube-admin-login-link" style="margin-top: 14px;">' +
( isLocalLogin
? '<span>⚙️ Yönetici (Local Admin) Giriş Modu Aktif</span>'
: '<a href="#" id="yaktube-show-admin-login">⚙️ Sunucu Yöneticisi Girişi</a>' ) +
'</div>' ;
var formFirstChild = loginCard . firstElementChild ;
if ( formFirstChild ) {
loginCard . insertBefore ( ssoBox , formFirstChild );
} else {
loginCard . appendChild ( ssoBox );
}
var registerLinks = document . querySelectorAll ( 'a[href*="/signup"], a[href*="/register"]' );
registerLinks . forEach ( function ( l ) {
l . href = 'https://auth.yakhub.com.tr/register' ;
l . target = '_blank' ;
l . innerText = 'YakNet Hesabı Oluştur' ;
});
var formInputs = loginCard . querySelector ( '.form-group, .inputs, form' ) || loginCard . querySelector ( 'input' );
var containerToHide = formInputs ? formInputs . closest ( 'form' ) || formInputs . parentElement : null ;
if ( containerToHide && ! isLocalLogin ) {
var subElements = Array . from ( loginCard . children ). filter ( function ( el ) {
return (
el . id !== 'yaknet-login-section' && ! el . classList . contains ( 'title' ) && ! el . classList . contains ( 'page-title' )
);
});
subElements . forEach ( function ( el ) {
el . style . display = 'none' ;
});
var showAdminBtn = document . getElementById ( 'yaktube-show-admin-login' );
if ( showAdminBtn ) {
showAdminBtn . addEventListener ( 'click' , function ( e ) {
e . preventDefault ();
2026-08-31 14:04:28 +03:00
openLocalAdminLoginModal ();
2026-08-29 12:42:36 +03:00
});
}
}
}
}
// 3. Intelligent Branding & A11y DOM Auto-Fixer
function autoFixDOM () {
2026-08-30 11:38:46 +03:00
injectAccessiblePlayerBar ();
2026-08-31 14:04:28 +03:00
// Check if on 403 / unauthorized page and provide admin login button
var notAuthorizedEl = document . querySelector ( 'my-not-found, .not-found, .forbidden, my-forbidden, .error-page' );
if ( notAuthorizedEl && ! document . getElementById ( 'yaktube-403-admin-btn' )) {
var btn403 = document . createElement ( 'div' );
btn403 . id = 'yaktube-403-admin-btn' ;
btn403 . style . margin = '20px auto' ;
btn403 . style . textAlign = 'center' ;
btn403 . innerHTML =
'<button style="background:linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%); color:#fff; border:none; padding:12px 24px; border-radius:10px; font-weight:700; cursor:pointer; font-size:15px;">🔑 Sunucu Yöneticisi Olarak Giriş Yap (Alt+A)</button>' ;
btn403 . querySelector ( 'button' ). addEventListener ( 'click' , openLocalAdminLoginModal );
notAuthorizedEl . appendChild ( btn403 );
}
2026-08-29 12:42:36 +03:00
injectYakNetSSO ();
2026-08-31 14:33:55 +03:00
// Replace all login buttons labels and titles
document
. querySelectorAll (
'my-login-link, a[href*="/login"], a[title*="Giriş sayfası na git"], [aria-label*="Giriş sayfası na git"]'
)
. forEach ( function ( el ) {
if ( el . getAttribute ( 'title' ) && el . getAttribute ( 'title' ). includes ( 'Giriş sayfası na git' )) {
el . setAttribute ( 'title' , 'YakNet ile Giriş Yap' );
}
if ( el . getAttribute ( 'aria-label' ) && el . getAttribute ( 'aria-label' ). includes ( 'Giriş sayfası na git' )) {
el . setAttribute ( 'aria-label' , 'YakNet ile Giriş Yap' );
}
var anchor = el . tagName . toLowerCase () === 'a' ? el : el . querySelector ( 'a' );
if ( anchor ) {
anchor . setAttribute ( 'title' , 'YakNet ile Giriş Yap' );
anchor . setAttribute ( 'aria-label' , 'YakNet ile Giriş Yap' );
var spans = anchor . querySelectorAll ( 'span' );
spans . forEach ( function ( s ) {
if (
s . textContent . includes ( 'Giriş sayfası na git' ) ||
s . textContent . trim () === 'Giriş' ||
s . textContent . trim () === 'Giriş yap'
) {
s . textContent = 'YakNet ile Giriş Yap' ;
}
});
}
});
2026-08-29 12:42:36 +03:00
// Aggressively remove generic Framasoft Open-in-App / Play Store banners
var genericAppBanners = document . querySelectorAll (
'my-open-app, my-client-app-banner, .open-app-container, .app-banner, [class*="open-app"], [class*="open-in-app"], .mobile-app-banner, my-open-in-app, .app-promotion, a[href*="play.google.com/store/apps/details?id=app.joinpeertube"], a[href*="f-droid.org"]'
);
genericAppBanners . forEach ( function ( banner ) {
banner . setAttribute ( 'aria-hidden' , 'true' );
banner . setAttribute ( 'tabindex' , '-1' );
if ( banner . parentElement ) {
try {
banner . remove ();
} catch ( e ) {
banner . style . display = 'none' ;
}
}
});
// Replace leftover PeerTube branding in visible UI texts
var walker = document . createTreeWalker ( document . body , NodeFilter . SHOW_TEXT , null , false );
var node ;
while (( node = walker . nextNode ())) {
var val = node . nodeValue ;
if ( val && ( val . includes ( 'PeerTube' ) || val . includes ( 'Peertube' ))) {
var parentTag = node . parentElement ? node . parentElement . tagName : '' ;
var parentElem = node . parentElement ;
if (
parentElem &&
( parentElem . closest ( '#yaktube-ondemand-container' ) ||
parentElem . closest ( '#yaktube-live-watch-container' ) ||
parentElem . tagName === 'SCRIPT' ||
parentElem . tagName === 'STYLE' )
) {
continue ;
}
if ( true ) {
node . nodeValue = val
. replace ( /PeerTube hakkı nda/gi , 'YakTube Hakkı nda' )
. replace ( /PeerTube'a katı lı n/gi , "YakTube'a Katı lı n" )
. replace ( /PeerTube örneği/gi , 'YakTube Sunucusu' )
2026-08-31 14:33:55 +03:00
. replace ( /PeerTube/g , 'YakTube' )
. replace ( /Giriş sayfası na git/gi , 'YakNet ile Giriş Yap' );
2026-08-29 12:42:36 +03:00
}
}
}
// Semantic Headings (H3 for video titles, H2 for sections)
document
. querySelectorAll ( '.video-miniature, my-video-miniature, .video-card, .yaktube-card, [class*="miniature"]' )
. forEach ( function ( card ) {
var titleElement = card . querySelector (
'.video-miniature-name, .video-name, .yaktube-video-name, a[title], .title'
);
if ( titleElement ) {
if ( ! [ 'H1' , 'H2' , 'H3' , 'H4' , 'H5' , 'H6' ]. includes ( titleElement . tagName )) {
titleElement . setAttribute ( 'role' , 'heading' );
titleElement . setAttribute ( 'aria-level' , '3' );
}
}
});
document
. querySelectorAll ( '.section-title, .sub-header, .videos-header, .page-header, .yaktube-title-h3' )
. forEach ( function ( sec ) {
if ( ! [ 'H1' , 'H2' ]. includes ( sec . tagName )) {
sec . setAttribute ( 'role' , 'heading' );
sec . setAttribute ( 'aria-level' , '2' );
}
});
// Make native PeerTube comment authors accessible as H3 headings for screen readers
document
. querySelectorAll ( 'my-video-comment, .video-comment, my-video-comment-thread, .comment, .comment-thread' )
. forEach ( function ( commentEl ) {
var authorEl = commentEl . querySelector (
'.display-name, .actor-name, .comment-author, a.actor-link, .video-comment-header-left a, .video-comment-header-left span'
);
if (
authorEl &&
! [ 'H1' , 'H2' , 'H3' , 'H4' , 'H5' , 'H6' ]. includes ( authorEl . tagName ) &&
! authorEl . getAttribute ( 'role' )
) {
authorEl . setAttribute ( 'role' , 'heading' );
authorEl . setAttribute ( 'aria-level' , '3' );
}
});
// Auto-fix unlabeled buttons
document . querySelectorAll ( 'button:not([data-a11y-fixed])' ). forEach ( function ( btn ) {
btn . setAttribute ( 'data-a11y-fixed' , 'true' );
var text = ( btn . textContent || '' ). trim ();
var hasAria = btn . getAttribute ( 'aria-label' ) || btn . getAttribute ( 'title' );
if ( ! text && ! hasAria ) {
var html = btn . innerHTML . toLowerCase ();
var className = ( btn . className || '' ). toLowerCase ();
var icon = btn . querySelector ( 'my-global-icon, i, svg, [class*="icon-"]' );
var iconName = icon ? icon . className || icon . getAttribute ( 'name' ) || '' : '' ;
if ( html . includes ( 'search' ) || className . includes ( 'search' ) || iconName . includes ( 'search' )) {
btn . setAttribute ( 'aria-label' , 'Ara' );
} else if ( html . includes ( 'bell' ) || className . includes ( 'notification' ) || iconName . includes ( 'bell' )) {
btn . setAttribute ( 'aria-label' , 'Bildirimler' );
} else if ( html . includes ( 'cog' ) || html . includes ( 'setting' ) || className . includes ( 'setting' )) {
btn . setAttribute ( 'aria-label' , 'Ayarlar' );
} else if (
html . includes ( 'menu' ) ||
html . includes ( 'bars' ) ||
className . includes ( 'burger' ) ||
className . includes ( 'menu' )
) {
btn . setAttribute ( 'aria-label' , 'Ana Gezinme Menüsü' );
} else if ( html . includes ( 'play' ) || className . includes ( 'play' )) {
btn . setAttribute ( 'aria-label' , 'Oynat' );
} else if ( html . includes ( 'pause' ) || className . includes ( 'pause' )) {
btn . setAttribute ( 'aria-label' , 'Duraklat' );
} else if ( html . includes ( 'volume' ) || html . includes ( 'mute' ) || className . includes ( 'volume' )) {
btn . setAttribute ( 'aria-label' , 'Ses Kontrolü' );
} else if ( html . includes ( 'fullscreen' ) || className . includes ( 'fullscreen' )) {
btn . setAttribute ( 'aria-label' , 'Tam Ekran' );
} else if (
html . includes ( 'ellipsis' ) ||
html . includes ( 'more' ) ||
className . includes ( 'more' ) ||
className . includes ( 'action' )
) {
btn . setAttribute ( 'aria-label' , 'Diğer İşlemler ve Seçenekler' );
} else if ( html . includes ( 'trash' ) || html . includes ( 'delete' ) || className . includes ( 'delete' )) {
btn . setAttribute ( 'aria-label' , 'Sil' );
} else if ( html . includes ( 'edit' ) || className . includes ( 'edit' )) {
btn . setAttribute ( 'aria-label' , 'Düzenle' );
} else if ( html . includes ( 'share' ) || className . includes ( 'share' )) {
btn . setAttribute ( 'aria-label' , 'Paylaş' );
} else if ( html . includes ( 'thumb-up' ) || html . includes ( 'like' ) || className . includes ( 'like' )) {
btn . setAttribute ( 'aria-label' , 'Beğen' );
} else if ( html . includes ( 'thumb-down' ) || html . includes ( 'dislike' ) || className . includes ( 'dislike' )) {
btn . setAttribute ( 'aria-label' , 'Beğenme' );
} else if ( html . includes ( 'filter' ) || className . includes ( 'filter' )) {
btn . setAttribute ( 'aria-label' , 'Filtreler' );
} else if ( html . includes ( 'close' ) || html . includes ( 'times' ) || className . includes ( 'close' )) {
btn . setAttribute ( 'aria-label' , 'Kapat' );
} else if ( btn . closest ( '.vjs-control-bar' )) {
btn . setAttribute ( 'aria-label' , 'Oynatı cı Düğmesi' );
} else {
var parent = btn . parentElement ;
if ( parent && parent . title ) {
btn . setAttribute ( 'aria-label' , parent . title );
} else {
btn . setAttribute ( 'aria-label' , 'Düğme' );
}
}
}
});
// Auto-fix unlabeled images
document . querySelectorAll ( 'img:not([data-a11y-fixed])' ). forEach ( function ( img ) {
img . setAttribute ( 'data-a11y-fixed' , 'true' );
var alt = img . getAttribute ( 'alt' );
if ( ! alt || alt . trim () === '' ) {
var card = img . closest ( '.video-miniature, .video-card, .yaktube-card, [class*="miniature"]' );
if ( card ) {
var titleEl = card . querySelector ( '.video-miniature-name, .video-name, .yaktube-video-name, h3, h4, a[title]' );
var titleText = titleEl ? ( titleEl . textContent || titleEl . getAttribute ( 'title' ) || '' ). trim () : '' ;
if ( titleText ) {
img . setAttribute ( 'alt' , titleText + ' video kapağı' );
return ;
}
}
var avatarContainer = img . closest ( '.actor-avatar, .channel-avatar, .avatar, [class*="avatar"]' );
if ( avatarContainer ) {
var nameEl = avatarContainer . parentElement
? avatarContainer . parentElement . querySelector ( '.name, .actor-name, .channel-name' )
: null ;
var nameText = nameEl ? ( nameEl . textContent || '' ). trim () : '' ;
img . setAttribute ( 'alt' , ( nameText ? nameText + ' ' : '' ) + 'Profil Resmi' );
return ;
}
if ( img . src && ( img . src . includes ( 'logo' ) || img . className . includes ( 'logo' ))) {
img . setAttribute ( 'alt' , getInstanceName () + ' Logo' );
return ;
}
var parentLink = img . closest ( 'a' );
if ( parentLink && parentLink . textContent && parentLink . textContent . trim (). length > 0 ) {
img . setAttribute ( 'alt' , '' );
} else if ( parentLink && parentLink . getAttribute ( 'title' )) {
img . setAttribute ( 'alt' , parentLink . getAttribute ( 'title' ));
} else {
img . setAttribute ( 'alt' , 'Görsel' );
}
}
});
// Auto-fix unlabeled links
document . querySelectorAll ( 'a:not([data-a11y-fixed])' ). forEach ( function ( a ) {
a . setAttribute ( 'data-a11y-fixed' , 'true' );
var text = ( a . textContent || '' ). trim ();
var hasAria = a . getAttribute ( 'aria-label' ) || a . getAttribute ( 'title' );
if ( ! text && ! hasAria ) {
var img = a . querySelector ( 'img' );
if ( img && img . alt ) {
a . setAttribute ( 'aria-label' , img . alt );
} else if ( a . href ) {
if ( a . href . includes ( '/videos/watch/' )) {
a . setAttribute ( 'aria-label' , 'Videoyu İzle' );
} else if ( a . href . includes ( '/video-channels/' )) {
a . setAttribute ( 'aria-label' , 'Kanal Sayfası na Git' );
} else if ( a . href . includes ( '/accounts/' )) {
a . setAttribute ( 'aria-label' , 'Hesap Profiline Git' );
} else if ( a . href . includes ( '/videos/search' )) {
a . setAttribute ( 'aria-label' , 'Arama Sayfası ' );
} else {
a . setAttribute ( 'aria-label' , 'Bağlantı ' );
}
}
}
});
// Auto-fix search inputs
document . querySelectorAll ( 'input:not([data-a11y-fixed])' ). forEach ( function ( inp ) {
inp . setAttribute ( 'data-a11y-fixed' , 'true' );
if ( ! inp . getAttribute ( 'aria-label' ) && ! inp . getAttribute ( 'placeholder' ) && ! ( inp . labels && inp . labels . length )) {
var type = ( inp . getAttribute ( 'type' ) || 'text' ). toLowerCase ();
if ( type === 'search' ) {
inp . setAttribute ( 'aria-label' , getInstanceName () + ' Video Arama' );
} else if ( type === 'checkbox' ) {
inp . setAttribute ( 'aria-label' , 'Seçim Kutusu' );
} else if ( type === 'password' ) {
inp . setAttribute ( 'aria-label' , 'Åifre' );
}
}
});
}
setInterval ( autoFixDOM , 1000 );
2026-08-30 11:38:46 +03:00
// 3. Screen-Reader & Turkish Q Optimized Shortcuts & Player Bar (v1.4.1)
2026-08-29 12:42:36 +03:00
function toggleShortcutsModal () {
var modal = document . getElementById ( 'yaktube-shortcuts-dialog' );
if ( modal ) {
if ( modal . style . display === 'none' || ! modal . style . display ) {
modal . style . display = 'flex' ;
2026-08-30 02:21:21 +03:00
announce ( 'Klavye kı sayolları yardı m penceresi açıldı . Kapatmak için Escape tuşuna bası n.' );
2026-08-29 12:42:36 +03:00
var closeBtn = document . getElementById ( 'yaktube-shortcuts-close' );
if ( closeBtn ) closeBtn . focus ();
} else {
modal . style . display = 'none' ;
announce ( 'Yardı m penceresi kapatı ldı .' );
}
} else {
modal = document . createElement ( 'div' );
modal . id = 'yaktube-shortcuts-dialog' ;
modal . className = 'yaktube-modal-overlay' ;
modal . setAttribute ( 'role' , 'dialog' );
modal . setAttribute ( 'aria-modal' , 'true' );
modal . setAttribute ( 'aria-labelledby' , 'yaktube-shortcuts-title' );
modal . innerHTML =
2026-08-30 11:38:46 +03:00
'<div class="yaktube-modal-card" style="max-width: 600px; background:#1e1e24; border-radius:18px; padding:24px; color:#fff; box-shadow:0 16px 40px rgba(0,0,0,0.8);">' +
2026-08-30 02:21:21 +03:00
'<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">' +
2026-08-30 11:38:46 +03:00
'<h3 id="yaktube-shortcuts-title" style="margin:0; font-size:18px; color:#ff8f37;">⌨️ Klavye Kı sayolları (Ekran Okuyucu Uyumlu)</h3>' +
2026-08-30 02:21:21 +03:00
'<button id="yaktube-shortcuts-close" class="yaktube-voice-modal-close-btn" aria-label="Kapat">✕</button>' +
'</div>' +
2026-08-30 11:38:46 +03:00
'<div style="font-size:12px; color:#aaa; margin-bottom:14px;">💡 <b>Ekran Okuyucu Notu:</b> Sanal imleç (Browse mode) açıkken kı sayolları <b>Alt</b> tuşuyla birlikte kullanabilir veya video oynatı cı butonları ndan faydalanabilirsiniz.</div>' +
2026-08-30 02:21:21 +03:00
'<table class="yaktube-shortcuts-table" style="width:100%; border-collapse:collapse; font-size:13px;">' +
2026-08-30 11:38:46 +03:00
'<thead><tr style="border-bottom:1px solid rgba(255,255,255,0.1); text-align:left; color:#aaa;"><th style="padding:6px 0;">Kı sayol (Evrensel / Tek Tuş)</th><th style="padding:6px 0;">İşlev</th></tr></thead>' +
2026-08-29 12:42:36 +03:00
'<tbody>' +
2026-08-30 11:38:46 +03:00
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + K</span> veya <span class="yaktube-kbd">Space</span></td><td>Oynat / Duraklat</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + J</span> veya <span class="yaktube-kbd">J</span></td><td>10 saniye geri sar</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + L</span> veya <span class="yaktube-kbd">L</span></td><td>10 saniye ileri sar</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + Yukarı / Aşağı</span></td><td>Sesi %5 artı r / azalt</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + M</span> veya <span class="yaktube-kbd">M</span></td><td>Sesi Kapat / Aç (Mute)</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + T</span> veya <span class="yaktube-kbd">T</span></td><td>Sinema / Tiyatro Modu</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + I</span> veya <span class="yaktube-kbd">I</span></td><td>Hı zlı Video Bilgisi & İstatistikler</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + C</span> veya <span class="yaktube-kbd">C</span></td><td>Altyazı ları Aç / Kapat</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + .</span> / <span class="yaktube-kbd">Alt + ,</span></td><td>Oynatma hı zı nı artı r / azalt</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + S</span> veya <span class="yaktube-kbd">/</span> (Shift+7)</td><td>Arama penceresini aç</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + V</span> veya <span class="yaktube-kbd">Shift + V</span></td><td>Sesli aramayı başlat</td></tr>' +
'<tr><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + H</span> veya <span class="yaktube-kbd">F1</span> veya <span class="yaktube-kbd">?</span></td><td>Bu kı lavuzu aç / kapat</td></tr>' +
2026-08-29 12:42:36 +03:00
'</tbody>' +
'</table>' +
'</div>' ;
document . body . appendChild ( modal );
2026-08-30 02:21:21 +03:00
var closeBtn = modal . querySelector ( '#yaktube-shortcuts-close' );
if ( closeBtn ) {
closeBtn . addEventListener ( 'click' , function () {
modal . style . display = 'none' ;
});
}
modal . addEventListener ( 'click' , function ( e ) {
if ( e . target === modal ) modal . style . display = 'none' ;
2026-08-29 12:42:36 +03:00
});
2026-08-30 02:21:21 +03:00
announce ( 'Klavye kı sayolları yardı m penceresi açıldı . Kapatmak için Escape tuşuna bası n.' );
2026-08-29 12:42:36 +03:00
}
}
function isInputFocused () {
var el = document . activeElement ;
if ( ! el ) return false ;
var tag = el . tagName ? el . tagName . toUpperCase () : '' ;
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el . isContentEditable ;
}
function getVideoElement () {
return document . querySelector ( 'video' );
}
function formatTime ( seconds ) {
if ( isNaN ( seconds ) || seconds < 0 ) return '0 saniye' ;
var mins = Math . floor ( seconds / 60 );
var secs = Math . floor ( seconds % 60 );
if ( mins === 0 ) return secs + ' saniye' ;
return mins + ' dakika ' + secs + ' saniye' ;
}
2026-08-30 02:21:21 +03:00
function showQuickVideoInfo () {
var video = getVideoElement ();
if ( ! video ) {
announce ( 'Şu anda aktif oynatı lan bir video bulunmuyor.' );
return ;
}
var titleEl = document . querySelector ( 'h1.video-title, .video-name, my-video-watch h1, .title' );
var title = titleEl ? titleEl . textContent . trim () : 'Video' ;
var curTime = formatTime ( video . currentTime );
var totalTime = formatTime ( video . duration || 0 );
var remaining = formatTime (( video . duration || 0 ) - video . currentTime );
2026-08-30 11:38:46 +03:00
var speed = ( video . playbackRate || 1 ) + 'x' ;
var volume = Math . round (( video . volume || 1 ) * 100 ) + '%' ;
2026-08-30 02:21:21 +03:00
var quality = video . videoHeight ? video . videoHeight + 'p' : 'Otomatik' ;
// Visual Toast
var existingToast = document . querySelector ( '.yaktube-video-info-toast' );
if ( existingToast ) existingToast . remove ();
var toast = document . createElement ( 'div' );
toast . className = 'yaktube-video-info-toast' ;
toast . innerHTML =
'<div class="yaktube-info-toast-title">📊 Video Bilgisi & Durum</div>' +
'<div class="yaktube-info-toast-row"><span>Başlı k:</span> <b>' +
( title . length > 28 ? title . substring ( 0 , 28 ) + '...' : title ) +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Konum / Süre:</span> <b>' +
curTime +
' / ' +
totalTime +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Kalan Süre:</span> <b>' +
remaining +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Çözünürlük:</span> <b>' +
quality +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Hı z & Ses:</span> <b>' +
speed +
' | ' +
volume +
'</b></div>' ;
document . body . appendChild ( toast );
setTimeout ( function () {
if ( toast . parentElement ) toast . remove ();
}, 4000 );
announce (
'Video Bilgisi: ' +
title +
'. Konum: ' +
curTime +
', Toplam Süre: ' +
totalTime +
', Kalan: ' +
remaining +
', Oynatma Hı zı : ' +
speed +
', Ses: yüzde ' +
Math . round ( video . volume * 100 ) +
', Çözünürlük: ' +
quality ,
true
);
}
var speedSteps = [ 0.25 , 0.5 , 0.75 , 1 , 1.25 , 1.5 , 1.75 , 2 ];
2026-08-30 11:38:46 +03:00
function adjustPlaybackSpeed ( increase ) {
var video = getVideoElement ();
if ( ! video ) return ;
var curSpeed = video . playbackRate || 1 ;
if ( increase ) {
var nextIdx = speedSteps . findIndex ( function ( s ) {
return s > curSpeed ;
});
video . playbackRate = nextIdx !== - 1 ? speedSteps [ nextIdx ] : 2 ;
announce ( 'Oynatma hı zı ' + video . playbackRate + ' katı na çıkarı ldı .' );
} else {
var prevSteps = speedSteps . filter ( function ( s ) {
return s < curSpeed ;
});
video . playbackRate = prevSteps . length > 0 ? prevSteps [ prevSteps . length - 1 ] : 0.25 ;
announce ( 'Oynatma hı zı ' + video . playbackRate + ' katı na düşürüldü.' );
}
}
// 3.1. Injects Accessible Player Action Bar under the Watch Video
function injectAccessiblePlayerBar () {
if ( ! window . location . pathname . includes ( '/videos/watch/' )) return ;
var video = getVideoElement ();
if ( ! video ) return ;
var targetContainer = document . querySelector (
'my-video-watch, .video-watch, .watch-container, #yaktube-ondemand-container'
);
if ( ! targetContainer ) return ;
var existingBar = document . getElementById ( 'yaktube-player-action-bar' );
if ( existingBar ) return ;
var bar = document . createElement ( 'div' );
bar . id = 'yaktube-player-action-bar' ;
bar . className = 'yaktube-player-action-bar' ;
bar . setAttribute ( 'role' , 'toolbar' );
bar . setAttribute ( 'aria-label' , 'Erişilebilir Video Kontrolleri' );
bar . innerHTML =
'<span class="yaktube-player-bar-label">⚡ Oynatı cı Kontrolleri:</span>' +
'<button id="yaktube-bar-rewind-btn" class="yaktube-player-action-btn" aria-label="10 Saniye Geri Sar (Alt+J)">⏪ 10sn Geri</button>' +
'<button id="yaktube-bar-play-btn" class="yaktube-player-action-btn" aria-label="Oynat veya Duraklat (Alt+K)">⏯️ Oynat/Duraklat</button>' +
'<button id="yaktube-bar-forward-btn" class="yaktube-player-action-btn" aria-label="10 Saniye İleri Sar (Alt+L)">⏩ 10sn İleri</button>' +
'<button id="yaktube-bar-speed-btn" class="yaktube-player-action-btn" aria-label="Oynatma Hı zı nı Değiştir (Alt+.)">⚡ Hı z</button>' +
'<button id="yaktube-bar-theater-btn" class="yaktube-player-action-btn" aria-label="Sinema Modunu Aç/Kapat (Alt+T)">🎭 Sinema</button>' +
'<button id="yaktube-bar-info-btn" class="yaktube-player-action-btn" aria-label="Video Bilgisi ve İstatistikleri (Alt+I)">📊 Bilgi</button>' +
'<button id="yaktube-bar-help-btn" class="yaktube-player-action-btn" aria-label="Kı sayol Kı lavuzu (Alt+H veya F1)">⌨️ Kı sayollar</button>' ;
var playerWrap =
document . querySelector ( '.player-container, .video-player-container, .player-wrapper, my-video-watch .player' ) ||
targetContainer ;
if ( playerWrap && playerWrap . nextSibling ) {
playerWrap . parentNode . insertBefore ( bar , playerWrap . nextSibling );
} else {
targetContainer . prepend ( bar );
}
// Attach button actions
bar . querySelector ( '#yaktube-bar-rewind-btn' ). addEventListener ( 'click' , function () {
if ( video ) {
video . currentTime = Math . max ( 0 , video . currentTime - 10 );
announce ( '10 saniye geri sarı ldı . Konum: ' + formatTime ( video . currentTime ));
}
});
bar . querySelector ( '#yaktube-bar-play-btn' ). addEventListener ( 'click' , function () {
if ( video ) {
if ( video . paused ) {
video . play ();
announce ( 'Video oynatı lı yor.' );
} else {
video . pause ();
announce ( 'Video duraklatı ldı .' );
}
}
});
bar . querySelector ( '#yaktube-bar-forward-btn' ). addEventListener ( 'click' , function () {
if ( video ) {
video . currentTime = Math . min ( video . duration || 99999 , video . currentTime + 10 );
announce ( '10 saniye ileri sarı ldı . Konum: ' + formatTime ( video . currentTime ));
}
});
bar . querySelector ( '#yaktube-bar-speed-btn' ). addEventListener ( 'click' , function () {
adjustPlaybackSpeed ( true );
});
bar . querySelector ( '#yaktube-bar-theater-btn' ). addEventListener ( 'click' , function () {
document . body . classList . toggle ( 'yaktube-theater-mode' );
var isT = document . body . classList . contains ( 'yaktube-theater-mode' );
announce ( isT ? 'Sinema modu açıldı .' : 'Sinema modundan çıkı ldı .' );
});
bar . querySelector ( '#yaktube-bar-info-btn' ). addEventListener ( 'click' , function () {
showQuickVideoInfo ();
});
bar . querySelector ( '#yaktube-bar-help-btn' ). addEventListener ( 'click' , function () {
toggleShortcutsModal ();
});
}
// 4. Global Keydown Listener (Screen-Reader & Turkish Q Optimized)
2026-08-29 12:42:36 +03:00
window . addEventListener ( 'keydown' , function ( e ) {
if ( e . key === 'Escape' ) {
var shortcutsModal = document . getElementById ( 'yaktube-shortcuts-dialog' );
if ( shortcutsModal && shortcutsModal . style . display !== 'none' ) {
shortcutsModal . style . display = 'none' ;
announce ( 'Pencere kapatı ldı .' );
return ;
}
var importModal = document . getElementById ( 'yaktube-import-modal' );
if ( importModal && importModal . style . display !== 'none' ) {
importModal . style . display = 'none' ;
announce ( 'İndirme penceresi kapatı ldı .' );
return ;
}
if ( isInputFocused ()) {
document . activeElement . blur ();
}
return ;
}
2026-08-31 14:04:28 +03:00
// Admin Login: Alt+A
if ( e . altKey && ( e . key === 'a' || e . key === 'A' ) && ! isInputFocused ()) {
e . preventDefault ();
openLocalAdminLoginModal ();
return ;
}
2026-08-30 11:38:46 +03:00
// Help Dialog: Alt+H, F1 or '?' key
if (( e . altKey && ( e . key === 'h' || e . key === 'H' )) || e . key === 'F1' || e . key === '?' ) {
2026-08-29 12:42:36 +03:00
if ( ! isInputFocused ()) {
e . preventDefault ();
toggleShortcutsModal ();
return ;
}
}
2026-08-30 11:38:46 +03:00
// Search Open: Alt+S, Ctrl+K or '/' key (Turkish Q Shift+7)
if (
(( e . altKey && ( e . key === 's' || e . key === 'S' )) ||
( e . ctrlKey && ( e . key === 'k' || e . key === 'K' )) ||
e . key === '/' ) &&
! isInputFocused ()
) {
e . preventDefault ();
openFullSearchModal ( '' , false );
return ;
}
// Voice Search Open: Alt+V or Shift+V
if (
(( e . altKey && ( e . key === 'v' || e . key === 'V' )) || ( e . shiftKey && ( e . key === 'v' || e . key === 'V' ))) &&
! isInputFocused ()
) {
e . preventDefault ();
openFullSearchModal ( '' , true );
return ;
}
2026-08-29 12:42:36 +03:00
if ( isInputFocused ()) {
return ;
}
var video = getVideoElement ();
if ( ! video ) return ;
var key = e . key . toLowerCase ();
2026-08-30 11:38:46 +03:00
var alt = e . altKey ;
2026-08-29 12:42:36 +03:00
2026-08-30 11:38:46 +03:00
// Play / Pause (Alt+K, Space or K)
if ( key === ' ' || key === 'k' || ( alt && key === 'k' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
if ( video . paused ) {
video . play ();
announce ( 'Video oynatı lı yor.' );
} else {
video . pause ();
announce ( 'Video duraklatı ldı .' );
}
2026-08-30 02:21:21 +03:00
}
2026-08-30 11:38:46 +03:00
// Seek 10s Back / Forward (Alt+J / Alt+L or J / L)
else if ( key === 'j' || ( alt && key === 'j' )) {
2026-08-30 02:21:21 +03:00
e . preventDefault ();
video . currentTime = Math . max ( 0 , video . currentTime - 10 );
announce ( '10 saniye geri sarı ldı . Konum: ' + formatTime ( video . currentTime ));
2026-08-30 11:38:46 +03:00
} else if ( key === 'l' || ( alt && key === 'l' )) {
2026-08-30 02:21:21 +03:00
e . preventDefault ();
video . currentTime = Math . min ( video . duration || 99999 , video . currentTime + 10 );
announce ( '10 saniye ileri sarı ldı . Konum: ' + formatTime ( video . currentTime ));
}
2026-08-30 11:38:46 +03:00
// Seek 5s Back / Forward (ArrowLeft / ArrowRight or Alt+Left/Right)
else if ( e . key === 'ArrowLeft' || ( alt && e . key === 'ArrowLeft' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
video . currentTime = Math . max ( 0 , video . currentTime - 5 );
2026-08-30 02:21:21 +03:00
announce ( '5 saniye geri sarı ldı . Konum: ' + formatTime ( video . currentTime ));
2026-08-30 11:38:46 +03:00
} else if ( e . key === 'ArrowRight' || ( alt && e . key === 'ArrowRight' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
video . currentTime = Math . min ( video . duration || 99999 , video . currentTime + 5 );
2026-08-30 02:21:21 +03:00
announce ( '5 saniye ileri sarı ldı . Konum: ' + formatTime ( video . currentTime ));
}
2026-08-30 11:38:46 +03:00
// Volume Up / Down (ArrowUp / ArrowDown or Alt+Up/Down)
else if ( e . key === 'ArrowUp' || ( alt && e . key === 'ArrowUp' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
2026-08-30 11:38:46 +03:00
video . volume = Math . min ( 1 , ( video . volume || 1 ) + 0.05 );
2026-08-29 12:42:36 +03:00
announce ( 'Ses seviyesi yüzde ' + Math . round ( video . volume * 100 ));
2026-08-30 11:38:46 +03:00
} else if ( e . key === 'ArrowDown' || ( alt && e . key === 'ArrowDown' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
2026-08-30 11:38:46 +03:00
video . volume = Math . max ( 0 , ( video . volume || 1 ) - 0.05 );
2026-08-29 12:42:36 +03:00
announce ( 'Ses seviyesi yüzde ' + Math . round ( video . volume * 100 ));
2026-08-30 02:21:21 +03:00
}
2026-08-30 11:38:46 +03:00
// Mute (Alt+M or M)
else if ( key === 'm' || ( alt && key === 'm' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
video . muted = ! video . muted ;
announce ( video . muted ? 'Ses kapatı ldı (Sessiz).' : 'Ses açıldı .' );
2026-08-30 02:21:21 +03:00
}
2026-08-30 11:38:46 +03:00
// Fullscreen (Alt+F or F)
else if ( key === 'f' || ( alt && key === 'f' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
if ( ! document . fullscreenElement ) {
var container = video . closest ( '.video-js' ) || video . parentElement || video ;
if ( container . requestFullscreen ) container . requestFullscreen ();
announce ( 'Tam ekran moduna geçildi.' );
} else {
if ( document . exitFullscreen ) document . exitFullscreen ();
announce ( 'Tam ekrandan çıkı ldı .' );
}
2026-08-30 02:21:21 +03:00
}
2026-08-30 11:38:46 +03:00
// Theater Mode (Alt+T or T)
else if ( key === 't' || ( alt && key === 't' )) {
2026-08-30 02:21:21 +03:00
e . preventDefault ();
document . body . classList . toggle ( 'yaktube-theater-mode' );
var isTheater = document . body . classList . contains ( 'yaktube-theater-mode' );
announce ( isTheater ? 'Sinema modu açıldı .' : 'Sinema modundan çıkı ldı .' );
}
2026-08-30 11:38:46 +03:00
// Quick Video Info (Alt+I or I)
else if ( key === 'i' || ( alt && key === 'i' )) {
2026-08-30 02:21:21 +03:00
e . preventDefault ();
showQuickVideoInfo ();
}
2026-08-30 11:38:46 +03:00
// Subtitles (Alt+C or C)
else if ( key === 'c' || ( alt && key === 'c' )) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
var tracks = video . textTracks ;
if ( tracks && tracks . length > 0 ) {
var isShowing = tracks [ 0 ]. mode === 'showing' ;
tracks [ 0 ]. mode = isShowing ? 'disabled' : 'showing' ;
announce ( isShowing ? 'Altyazı lar kapatı ldı .' : 'Altyazı lar açıldı .' );
} else {
announce ( 'Bu videoda altyazı bulunmuyor.' );
}
2026-08-30 02:21:21 +03:00
}
2026-08-30 11:38:46 +03:00
// Speed Controls (Alt+. / Alt+, or > / <)
else if (( alt && ( e . key === '.' || e . key === '>' )) || e . key === '>' ) {
2026-08-30 02:21:21 +03:00
e . preventDefault ();
2026-08-30 11:38:46 +03:00
adjustPlaybackSpeed ( true );
} else if (( alt && ( e . key === ',' || e . key === '<' )) || e . key === '<' ) {
2026-08-30 02:21:21 +03:00
e . preventDefault ();
2026-08-30 11:38:46 +03:00
adjustPlaybackSpeed ( false );
2026-08-30 02:21:21 +03:00
}
// Percentage Jumps (0 - 9)
else if ( key >= '0' && key <= '9' && ! e . ctrlKey && ! e . altKey && ! e . metaKey ) {
2026-08-29 12:42:36 +03:00
e . preventDefault ();
var percent = parseInt ( key , 10 ) * 10 ;
if ( video . duration ) {
video . currentTime = ( video . duration * percent ) / 100 ;
2026-08-30 02:21:21 +03:00
announce ( 'Videonun yüzde ' + percent + ' noktası na atlandı . Konum: ' + formatTime ( video . currentTime ));
2026-08-29 12:42:36 +03:00
}
}
});
// 5. On-Demand Search & Import with Duplicate Awareness
var lastQuery = null ;
var isSearching = false ;
function showModal ( title , desc ) {
var overlay = document . getElementById ( 'yaktube-import-modal' );
if ( ! overlay ) {
overlay = document . createElement ( 'div' );
overlay . id = 'yaktube-import-modal' ;
overlay . className = 'yaktube-modal-overlay' ;
overlay . setAttribute ( 'role' , 'dialog' );
overlay . setAttribute ( 'aria-modal' , 'true' );
overlay . setAttribute ( 'aria-labelledby' , 'yaktube-modal-title' );
overlay . innerHTML =
'<div class="yaktube-modal-card">' +
'<div class="yaktube-spinner" aria-hidden="true"></div>' +
'<h3 class="yaktube-modal-title" id="yaktube-modal-title" style="margin-top:0;">' +
( title || "YakTube'a Aktarı lı yor..." ) +
'</h3>' +
'<p class="yaktube-modal-desc" id="yaktube-modal-desc">' +
( desc || 'Seçtiğiniz video D: diskinize aktarı lı yor...' ) +
'</p>' +
'</div>' ;
document . body . appendChild ( overlay );
} else {
var titleElem = document . getElementById ( 'yaktube-modal-title' );
var descElem = document . getElementById ( 'yaktube-modal-desc' );
if ( titleElem ) titleElem . innerText = title ;
if ( descElem ) descElem . innerText = desc ;
overlay . style . display = 'flex' ;
}
announce (( title || '' ) + '. ' + ( desc || '' ), true );
}
function hideModal () {
var overlay = document . getElementById ( 'yaktube-import-modal' );
if ( overlay ) overlay . style . display = 'none' ;
}
function handleLiveStream ( url , videoTitle , isRestoring ) {
var videoIdMatch = url . match ( /[?&]v=([^&]+)/ ) || url . match ( /youtu\.be\/([^?]+)/ );
var videoId = videoIdMatch ? videoIdMatch [ 1 ] : '' ;
if ( ! videoId ) {
alert ( 'Canl\u0131 yay\u0131n ID bulunamad\u0131.' );
return ;
}
// Persist active live stream state in sessionStorage
try {
sessionStorage . setItem (
'yaktube_active_live' ,
JSON . stringify ({
url : url ,
title : videoTitle ,
videoId : videoId
})
);
} catch ( e ) {}
var liveContainer = document . getElementById ( 'yaktube-live-watch-container' );
if ( liveContainer ) liveContainer . remove ();
liveContainer = document . createElement ( 'div' );
liveContainer . id = 'yaktube-live-watch-container' ;
liveContainer . className = 'yaktube-live-watch-section' ;
liveContainer . setAttribute ( 'role' , 'region' );
liveContainer . setAttribute ( 'tabindex' , '-1' );
liveContainer . setAttribute ( 'aria-label' , videoTitle + ' Canl\u0131 Yay\u0131n\u0131' );
liveContainer . innerHTML =
'<div style="background: #0f172a; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 10px 30px rgba(0,0,0,0.6); border: 1px solid rgba(255,255,255,0.08); outline: none;">' +
'<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; flex-wrap: wrap; gap: 10px;">' +
'<div style="display: flex; align-items: center; gap: 10px;">' +
'<span style="background: #dc2626; color: #fff; font-size: 12px; padding: 4px 10px; border-radius: 6px; font-weight: 700; display: inline-flex; align-items: center; gap: 6px;">\ud83d\udd34 CANLI YAYIN</span>' +
'<h1 id="yaktube-live-title" tabindex="0" style="margin: 0; font-size: 1.25rem; font-weight: 700; color: #fff; line-height: 1.3; outline: none;">' +
videoTitle +
'</h1>' +
'</div>' +
'<button id="yaktube-close-live-theater" style="background: #334155; color: #fff; border: none; padding: 8px 16px; border-radius: 8px; font-weight: 600; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px;" aria-label="Canl\u0131 Yay\u0131n \u0130zleme Alan\u0131n\u0131 Kapat (Escape)">' +
'\u2715 Yay\u0131n\u0131 Kapat (Esc)' +
'</button>' +
'</div>' +
'<div style="position: relative; padding-top: 56.25%; width: 100%; background: #000; border-radius: 10px; overflow: hidden; box-shadow: 0 8px 24px rgba(0,0,0,0.7);">' +
'<iframe id="yaktube-live-iframe" src="https://www.youtube-nocookie.com/embed/' +
videoId +
'?autoplay=1&rel=0&modestbranding=1&enablejsapi=1" title="' +
videoTitle . replace ( /"/g , '"' ) +
'" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>' +
'</div>' +
'<div style="margin-top: 14px; display: flex; justify-content: space-between; align-items: center; color: #94a3b8; font-size: 13px;">' +
'<div>\ud83d\udce1 YakTube Canl\u0131 Yay\u0131n Ak\u0131\u015f\u0131 \u2022 Reklams\u0131z ve Kesintisiz</div>' +
'<div style="color: #64748b;">ESC tu\u015funa basarak yay\u0131n\u0131 kapatabilirsiniz.</div>' +
'</div>' +
'<div id="yaktube-live-comments-panel" style="margin-top: 18px; background: #0b1120; border-radius: 10px; padding: 16px; border: 1px solid rgba(255,255,255,0.08);">' +
'<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">' +
'<h2 style="margin: 0; font-size: 15px; font-weight: 700; color: #fff; display: flex; align-items: center; gap: 8px;">' +
'\ud83d\udcac YouTube Topluluk Yorumlar\u0131' +
'</h2>' +
'<button id="yaktube-toggle-comments-btn" style="background: #ff8f37; color: #000; border: none; padding: 7px 16px; border-radius: 6px; font-weight: 700; cursor: pointer; font-size: 12px; display: inline-flex; align-items: center; gap: 6px;" aria-label="YouTube yorumlar\u0131n\u0131 y\u00fckle ve g\u00f6ster">' +
'\ud83d\udcac Yorumlar\u0131 G\u00f6ster (20 Yorum)' +
'</button>' +
'</div>' +
'<div id="yaktube-comments-list" style="display: none; margin-top: 14px; flex-direction: column; gap: 12px;"></div>' +
'<div id="yaktube-more-comments-wrap" style="text-align: center; margin-top: 16px; display: none;">' +
'<button id="yaktube-load-more-comments-btn" style="background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 8px 20px; border-radius: 8px; font-weight: 700; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px;" aria-label="Daha fazla yorum y\u00fckle">' +
'\u2795 Daha Fazla Yorum G\u00f6ster (+20 Yorum)' +
'</button>' +
'</div>' +
'</div>' +
'</div>' ;
var mainMount =
document . querySelector ( 'my-search, my-search-results, router-outlet, main, .main-col' ) || document . body ;
if ( mainMount && mainMount . firstChild ) {
mainMount . insertBefore ( liveContainer , mainMount . firstChild );
} else if ( mainMount ) {
mainMount . appendChild ( liveContainer );
}
if ( ! isRestoring ) {
liveContainer . scrollIntoView ({ behavior : 'smooth' , block : 'start' });
setTimeout ( function () {
var liveTitle = document . getElementById ( 'yaktube-live-title' );
if ( liveTitle ) liveTitle . focus ();
else liveContainer . focus ();
}, 150 );
announce (
videoTitle +
' canl\u0131 yay\u0131n\u0131 ba\u015flat\u0131ld\u0131. Canl\u0131 yay\u0131n alan\u0131na odaklan\u0131ld\u0131.' ,
true
);
}
var commentNextToken = null ;
var commentsList = document . getElementById ( 'yaktube-comments-list' );
var toggleCommentsBtn = document . getElementById ( 'yaktube-toggle-comments-btn' );
var moreCommentsWrap = document . getElementById ( 'yaktube-more-comments-wrap' );
var moreCommentsBtn = document . getElementById ( 'yaktube-load-more-comments-btn' );
function renderCommentCard ( c ) {
var card = document . createElement ( 'div' );
card . className = 'yaktube-comment-item' ;
card . setAttribute ( 'role' , 'article' );
card . setAttribute ( 'tabindex' , '0' );
card . setAttribute ( 'aria-label' , c . author + ' isimli kullan\u0131c\u0131n\u0131n yorumu: ' + c . content );
card . style . cssText =
'background: #1e293b; padding: 12px 14px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.05); display: flex; gap: 12px; align-items: flex-start;' ;
var avatarLetter = ( c . author || 'U' ). replace ( '@' , '' ). charAt ( 0 ). toUpperCase ();
var avatarHtml =
'<div style="width: 36px; height: 36px; border-radius: 50%; background: #334155; color: #ff8f37; font-weight: 700; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0;">' +
avatarLetter +
'</div>' ;
var safeContent = c . content
. replace ( /&/g , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( /\n/g , '<br>' );
card . innerHTML =
avatarHtml +
'<div style="flex-grow: 1; min-width: 0;">' +
'<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap;">' +
'<h3 class="yaktube-comment-author-heading" style="margin: 0; font-size: 13px; font-weight: 700; color: #f1f5f9; display: inline-block;">' +
c . author +
'</h3>' +
'<span style="color: #64748b; font-size: 11px;">' +
c . published +
'</span>' +
( c . likeCount && c . likeCount !== '0'
? '<span style="background: rgba(255,255,255,0.08); color: #94a3b8; font-size: 11px; padding: 2px 6px; border-radius: 4px; display: inline-flex; align-items: center; gap: 4px;">👍 ' +
c . likeCount +
'</span>'
: '' ) +
'</div>' +
'<div style="color: #cbd5e1; font-size: 13px; line-height: 1.45; word-break: break-word;">' +
safeContent +
'</div>' +
'</div>' ;
return card ;
}
function fetchComments ( token ) {
if ( ! token ) {
toggleCommentsBtn . disabled = true ;
toggleCommentsBtn . textContent = '⏳ Yorumlar Y\u00fckleniyor...' ;
} else {
moreCommentsBtn . disabled = true ;
moreCommentsBtn . textContent = '⏳ Y\u00fckleniyor...' ;
}
var fetchUrl =
'/api-custom/youtube-comments?id=' +
encodeURIComponent ( videoId ) +
( token ? '&token=' + encodeURIComponent ( token ) : '' );
fetch ( fetchUrl )
. then ( function ( r ) {
return r . json ();
})
. then ( function ( res ) {
if ( ! token ) {
commentsList . innerHTML = '' ;
commentsList . style . display = 'flex' ;
toggleCommentsBtn . textContent = '🔄 Yorumlar\u0131 Yenile' ;
toggleCommentsBtn . disabled = false ;
} else {
moreCommentsBtn . textContent = '➕ Daha Fazla Yorum G\u00f6ster (+20 Yorum)' ;
moreCommentsBtn . disabled = false ;
}
var list = res . comments || [];
if ( list . length === 0 && ! token ) {
commentsList . innerHTML =
'<div style="color: #94a3b8; font-size: 13px; text-align: center; padding: 12px;">Bu videoda/yay\u0131nda hen\u00fcz yorum bulunmuyor.</div>' ;
moreCommentsWrap . style . display = 'none' ;
announce ( 'Bu videoda henüz yorum bulunmuyor.' );
return ;
}
list . forEach ( function ( c ) {
commentsList . appendChild ( renderCommentCard ( c ));
});
commentNextToken = res . nextToken ;
if ( commentNextToken ) {
moreCommentsWrap . style . display = 'block' ;
} else {
moreCommentsWrap . style . display = 'none' ;
}
announce ( list . length + ' adet YouTube yorumu yüklendi.' );
})
. catch ( function ( err ) {
if ( ! token ) {
toggleCommentsBtn . disabled = false ;
toggleCommentsBtn . textContent = '⚠️ Tekrar Dene' ;
} else {
moreCommentsBtn . disabled = false ;
moreCommentsBtn . textContent = '⚠️ Tekrar Dene' ;
}
alert ( 'Yorumlar yüklenirken hata oluştu: ' + err . message );
});
}
if ( toggleCommentsBtn ) {
toggleCommentsBtn . addEventListener ( 'click' , function () {
fetchComments ( null );
});
}
if ( moreCommentsBtn ) {
moreCommentsBtn . addEventListener ( 'click' , function () {
if ( commentNextToken ) fetchComments ( commentNextToken );
});
}
function closeLiveTheater () {
try {
sessionStorage . removeItem ( 'yaktube_active_live' );
} catch ( e ) {}
if ( liveContainer && liveContainer . parentNode ) {
var ifr = liveContainer . querySelector ( 'iframe' );
if ( ifr ) ifr . src = '' ;
liveContainer . remove ();
}
document . removeEventListener ( 'keydown' , handleEsc );
announce ( 'Canl\u0131 yay\u0131n izleme alan\u0131 kapat\u0131ld\u0131.' );
}
function handleEsc ( e ) {
if ( e . key === 'Escape' || e . keyCode === 27 ) {
closeLiveTheater ();
}
}
var closeBtn = document . getElementById ( 'yaktube-close-live-theater' );
if ( closeBtn ) closeBtn . addEventListener ( 'click' , closeLiveTheater );
document . addEventListener ( 'keydown' , handleEsc );
}
function restoreActiveLiveStream () {
try {
var saved = sessionStorage . getItem ( 'yaktube_active_live' );
if ( ! saved ) return ;
var data = JSON . parse ( saved );
if ( data && data . url && data . title && ! document . getElementById ( 'yaktube-live-watch-container' )) {
handleLiveStream ( data . url , data . title , true );
}
} catch ( e ) {}
}
function handleImport ( url , btn , videoTitle ) {
btn . disabled = true ;
btn . innerHTML = '⏳ Aktarı lı yor...' ;
showModal (
'[1/2] 📥 Video İndiriliyor...' ,
( videoTitle ? '"' + videoTitle + '" ' : 'Video ' ) + "YouTube'dan sunucunuza aktarı lı yor. Lütfen bekleyin..."
);
fetch ( '/api-custom/ondemand-import' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON . stringify ({ targetUrl : url , title : videoTitle })
})
. then ( function ( res ) {
return res . json ();
})
. then ( function ( data ) {
if ( ! data || ! data . ok ) {
throw new Error ( data && data . error ? data . error : 'İçe aktarma başlatı lamadı .' );
}
var videoId = data . video . id ;
var videoUuid = data . video . uuid ;
var attempts = 0 ;
var pollInterval = setInterval ( function () {
attempts ++ ;
fetch ( '/api-custom/status/' + videoId )
. then ( function ( sRes ) {
return sRes . json ();
})
. then ( function ( sData ) {
if ( sData . isImporting ) {
showModal ( '[1/2] 📥 Video İndiriliyor...' , 'Video sunucuya indiriliyor (' + attempts * 2 + ' sn)...' );
} else if ( sData . isTranscoding ) {
showModal ( '[2/2] ⚙️ Oynatı cı Hazı rlanı yor...' , 'Video işleniyor ve yüksek kalite akış hazı rlanı yor...' );
}
if ( sData . isPublished || ( sData . hasFiles && attempts >= 8 )) {
clearInterval ( pollInterval );
showModal ( '🎉 Video Hazı r!' , 'Video kütüphanenize eklendi. Oynatı cı açılı yor...' );
setTimeout ( function () {
window . location . href = '/videos/watch/' + ( sData . uuid || videoUuid );
}, 600 );
} else if ( sData . isFailed ) {
clearInterval ( pollInterval );
hideModal ();
btn . disabled = false ;
btn . innerHTML = "📥 YakTube'a Aktar & İzle" ;
alert ( 'Video indirme sı rası nda bir hata oluştu.' );
announce ( 'Video indirme sı rası nda bir hata oluştu.' , true );
}
})
. catch ( function ( e ) {
console . error ( '[YakTube] Status poll error:' , e );
});
if ( attempts > 35 ) {
clearInterval ( pollInterval );
hideModal ();
btn . disabled = false ;
btn . innerHTML = "📥 YakTube'a Aktar & İzle" ;
alert (
'İndirme arka planda tamamlanı yor. Ana sayfada veya Videoları m sayfası nda kı sa süre içinde hazı r olacaktı r.'
);
announce ( 'İndirme arka planda tamamlanı yor. Ana sayfada kı sa süre içinde hazı r olacaktı r.' );
}
}, 1500 );
})
. catch ( function ( err ) {
hideModal ();
btn . disabled = false ;
btn . innerHTML = "📥 YakTube'a Aktar & İzle" ;
alert ( 'İndirme hatası : ' + err . message );
announce ( 'İndirme hatası : ' + err . message , true );
});
}
function checkAndInjectSearchResults () {
var url = new URL ( window . location . href );
var searchParam = url . searchParams . get ( 'search' );
if ( ! window . location . pathname . includes ( '/search' ) || ! searchParam || searchParam . trim (). length < 2 ) {
var existing = document . getElementById ( 'yaktube-ondemand-container' );
if ( existing ) existing . remove ();
lastQuery = null ;
return ;
}
var query = searchParam . trim ();
if ( query === lastQuery ) return ;
lastQuery = query ;
var container = document . getElementById ( 'yaktube-ondemand-container' );
if ( ! container ) {
container = document . createElement ( 'div' );
container . id = 'yaktube-ondemand-container' ;
container . className = 'yaktube-ondemand-section' ;
container . setAttribute ( 'role' , 'region' );
container . setAttribute ( 'aria-label' , 'Canl\u0131 YouTube Arama Sonu\u00e7lar\u0131' );
var mainContent =
document . querySelector ( 'my-search, my-search-results, router-outlet, main, .main-col' ) || document . body ;
if ( mainContent && mainContent . firstChild ) {
mainContent . insertBefore ( container , mainContent . firstChild );
} else if ( mainContent ) {
mainContent . appendChild ( container );
}
}
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">\u26a1 Canl\u0131 YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
query +
'" i\u00e7in YouTube Sonu\u00e7lar\u0131 Taran\u0131yor...</h2>' +
'</div>' +
'<button class="yaktube-shortcuts-btn" id="yaktube-open-shortcuts" aria-label="Klavye K\u0131sayollar\u0131 K\u0131lavuzunu A\u00e7">' +
'\u2328\ufe0f K\u0131sayollar (?)' +
'</button>' +
'</div>' +
'<div class="yaktube-loading">' +
'<div class="yaktube-spinner" aria-hidden="true"></div>' +
'<div style="margin-top: 10px;">YouTube\'daki en pop\u00fcler e\u015fle\u015fmeler taran\u0131yor...</div>' +
'</div>' ;
var shortcutsBtn = document . getElementById ( 'yaktube-open-shortcuts' );
if ( shortcutsBtn ) {
shortcutsBtn . addEventListener ( 'click' , toggleShortcutsModal );
}
announce ( query + ' i\u00e7in canl\u0131 YouTube aramas\u0131 ba\u015flat\u0131ld\u0131.' );
isSearching = true ;
fetch ( '/api-custom/youtube-search?q=' + encodeURIComponent ( query ))
. then ( function ( res ) {
return res . json ();
})
. then ( function ( data ) {
var rawResults = data . results || [];
var uniqueResults = [];
var seenIds = {};
rawResults . forEach ( function ( item ) {
if ( item && item . id && ! seenIds [ item . id ] && item . title ) {
seenIds [ item . id ] = true ;
uniqueResults . push ( item );
}
});
var count = uniqueResults . length ;
if ( count === 0 ) {
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
query +
'" için YouTube\'da Sonuç Bulunamadı </h2>' +
'</div>' +
'</div>' ;
announce ( query + " için YouTube\'da eşleşen video bulunamadı ." );
} else {
var countText = count === 1 ? '1 Video Bulundu' : count + ' Video Bulundu' ;
announce ( query + ' araması için ' + count + ' video listelendi.' );
var visibleCount = Math . min ( 12 , count );
function render () {
var visibleResults = uniqueResults . slice ( 0 , visibleCount );
var cardsHtml = '' ;
visibleResults . forEach ( function ( item ) {
var isLocal = !! item . isDownloaded ;
var isLive = !! item . isLive ;
var statusBadge = '' ;
if ( isLive ) {
statusBadge =
'<span class="yaktube-badge" style="background: #dc2626; color: #fff; margin-bottom: 6px; display: inline-block; font-weight: 700;">🔴 CANLI YAYIN</span>' ;
} else if ( isLocal ) {
statusBadge =
'<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); margin-bottom: 6px; display: inline-block;">✅ ' +
getInstanceName () +
"'da Mevcut</span>" ;
}
var actionButton = '' ;
if ( isLive ) {
actionButton =
'<button class="yaktube-live-btn" data-url="' +
item . url +
'" data-title="' +
item . title . replace ( /"/g , '"' ) +
'" style="background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%); color: #fff; border: none; padding: 8px 14px; border-radius: 6px; font-weight: 700; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' canlı yayı nı nı hemen izle">🔴 Canlı Yayı nı İzle</button>' ;
} else if ( isLocal ) {
actionButton =
'<a href="' +
item . localVideo . url +
'" class="yaktube-play-btn" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' videosunu YakTube\'dan hemen izle">▶️ ' +
getInstanceName () +
"'dan İzle</a>" ;
} else {
actionButton =
'<button class="yaktube-import-btn" data-url="' +
item . url +
'" data-title="' +
item . title . replace ( /"/g , '"' ) +
'" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' videosunu ' +
getInstanceName () +
'\'a indir ve izle">📥 ' +
getInstanceName () +
"'a Aktar & İzle</button>" ;
}
var durationText = isLive ? 'CANLI' : item . durationFormatted ;
var cardLabel =
item . title +
( isLive ? ' (Canlı Yayı n)' : isLocal ? ' (' + getInstanceName () + ' kütüphanenizde mevcut)' : '' ) +
', ' +
item . channel +
' kanalı , Süre: ' +
durationText ;
cardsHtml +=
'<div class="yaktube-card" role="article" tabindex="0" aria-label="' +
cardLabel . replace ( /"/g , '"' ) +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
item . thumbnail +
'" alt="' +
item . title . replace ( /"/g , '"' ) +
'" loading="lazy" />' +
'<span class="yaktube-duration-badge" style="' +
( isLive ? 'background:#dc2626;font-weight:700;' : '' ) +
'" aria-label="Süre">' +
durationText +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
statusBadge +
'<h3 class="yaktube-video-name" title="' +
item . title +
'" style="margin:0; font-size:13px; font-weight:600; line-height:1.35;">' +
item . title +
'</h3>' +
'<div class="yaktube-channel-name">' +
item . channel +
'</div>' +
'</div>' +
actionButton +
'</div>' +
'</div>' ;
});
var loadMoreHtml = '' ;
if ( visibleCount < count ) {
loadMoreHtml =
'<div style="text-align: center; margin: 20px 0 10px 0;">' +
'<button id="yaktube-load-more-btn" class="yaktube-load-more-btn" style="background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 10px 24px; border-radius: 8px; font-weight: 700; font-size: 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.3);" aria-label="Daha fazla video yükle">' +
'➕ Daha Fazla Video Göster (' +
visibleCount +
' / ' +
count +
')' +
'</button>' +
'</div>' ;
}
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
query +
'" için ' +
countText +
'</h2>' +
'</div>' +
'<button class="yaktube-shortcuts-btn" id="yaktube-open-shortcuts" aria-label="Klavye Kı sayolları Kı lavuzunu Aç">' +
'⌨️ Kı sayollar (?)' +
'</button>' +
'</div>' +
'<div class="yaktube-grid">' +
cardsHtml +
'</div>' +
loadMoreHtml ;
var sBtn = document . getElementById ( 'yaktube-open-shortcuts' );
if ( sBtn ) {
sBtn . addEventListener ( 'click' , toggleShortcutsModal );
}
var loadMoreBtn = document . getElementById ( 'yaktube-load-more-btn' );
if ( loadMoreBtn ) {
loadMoreBtn . addEventListener ( 'click' , function () {
visibleCount = Math . min ( visibleCount + 6 , count );
render ();
announce ( visibleCount + ' adet video gösteriliyor.' );
});
}
container . querySelectorAll ( '.yaktube-live-btn' ). forEach ( function ( btn ) {
btn . addEventListener ( 'click' , function ( e ) {
var liveUrl = e . currentTarget . getAttribute ( 'data-url' );
var liveTitle = e . currentTarget . getAttribute ( 'data-title' );
handleLiveStream ( liveUrl , liveTitle );
});
});
container . querySelectorAll ( '.yaktube-import-btn' ). forEach ( function ( btn ) {
btn . addEventListener ( 'click' , function ( e ) {
var videoUrl = e . currentTarget . getAttribute ( 'data-url' );
var videoTitle = e . currentTarget . getAttribute ( 'data-title' );
handleImport ( videoUrl , e . currentTarget , videoTitle );
});
});
}
render ();
}
})
. catch ( function ( err ) {
console . error ( '[YakTube] Search fetch error:' , err );
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">\u26a1 Canl\u0131 YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">Arama s\u0131ras\u0131nda bir ba\u011flant\u0131 sorunu olu\u015ftu.</h2>' +
'</div>' +
'</div>' ;
announce ( 'Arama s\u0131ras\u0131nda bir ba\u011flant\u0131 sorunu olu\u015ftu.' );
})
. finally ( function () {
isSearching = false ;
});
}
2026-08-29 19:22:42 +03:00
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
2026-08-29 22:37:27 +03:00
// --------------------------------------------------------------------------
2026-08-29 22:43:55 +03:00
// --------------------------------------------------------------------------
2026-08-29 22:59:18 +03:00
// --------------------------------------------------------------------------
2026-08-29 23:01:08 +03:00
// --------------------------------------------------------------------------
2026-08-29 21:47:30 +03:00
// --------------------------------------------------------------------------
2026-08-29 23:04:50 +03:00
// --------------------------------------------------------------------------
2026-08-29 23:06:17 +03:00
// --------------------------------------------------------------------------
2026-08-29 23:09:14 +03:00
// --------------------------------------------------------------------------
// 8. FULL YOUTUBE SEARCH & VOICE MODAL ENGINE (v1.3.3 - Guaranteed Close Fix)
2026-08-29 23:02:50 +03:00
// --------------------------------------------------------------------------
var activeVoiceRec = null ;
var isVoiceListening = false ;
2026-08-29 19:22:42 +03:00
2026-08-31 14:43:22 +03:00
// Dynamic Search History & Trending Helpers
function getRecentSearches () {
try {
var data = localStorage . getItem ( 'yaktube_recent_searches' );
return data ? JSON . parse ( data ) : [];
} catch ( e ) {
return [];
}
}
function saveRecentSearch ( query ) {
if ( ! query || query . trim (). length < 2 ) return ;
var q = query . trim ();
try {
var list = getRecentSearches ();
list = list . filter ( function ( item ) {
return item . toLowerCase () !== q . toLowerCase ();
});
list . unshift ( q );
if ( list . length > 8 ) list = list . slice ( 0 , 8 );
localStorage . setItem ( 'yaktube_recent_searches' , JSON . stringify ( list ));
} catch ( e ) {}
}
function clearRecentSearches () {
try {
localStorage . removeItem ( 'yaktube_recent_searches' );
} catch ( e ) {}
renderDynamicSearchChips ();
announce ( 'Arama geçmişi temizlendi.' );
}
var popularTrendingQueries = [
{ title : '🎵 Türkçe Pop' , q : 'Türkçe Pop' },
{ title : '🎸 Anadolu Rock' , q : 'Anadolu Rock' },
{ title : '🤖 Yapay Zeka' , q : 'Yapay Zeka' },
{ title : '💻 Yazı lı m Dersleri' , q : 'Yazı lı m Dersleri' },
{ title : '🎧 Sesli Kitap & Radyo Tiyatrosu' , q : 'Sesli Kitap' },
{ title : '📺 Canlı Haber' , q : 'Canlı Haber' },
{ title : '🌍 Bilim & Belgesel' , q : 'Bilim Belgeseli' },
{ title : '🎼 Akustik Canlı Performans' , q : 'Akustik Canlı ' },
{ title : '🎙️ Podcast & Söyleşi' , q : 'Podcast Türkçe' }
];
function renderDynamicSearchChips () {
var modal = document . getElementById ( 'yaktube-full-search-modal' );
if ( ! modal ) return ;
var container = modal . querySelector ( '#yaktube-modal-dynamic-chips-section' );
if ( ! container ) return ;
var recent = getRecentSearches ();
var html = '' ;
// 1. Recent searches section (if any)
if ( recent . length > 0 ) {
html +=
'<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">' +
'<div class="yaktube-modal-section-title" style="margin-bottom: 0;">🕒 Son Aramaları nı z</div>' +
'<button id="yaktube-clear-recent-btn" style="background: transparent; border: none; color: #ff8f37; font-size: 12px; font-weight: 600; cursor: pointer; text-decoration: underline;" aria-label="Arama geçmişini temizle">Geçmişi Temizle</button>' +
'</div>' +
'<div class="yaktube-modal-chips-grid" style="margin-bottom: 18px;">' ;
recent . forEach ( function ( term ) {
var safeTerm = term . replace ( /"/g , '"' );
html +=
'<button class="yaktube-modal-chip yaktube-recent-chip" data-query="' +
safeTerm +
'" aria-label="Son arama: ' +
safeTerm +
'">🕒 ' +
safeTerm +
'</button>' ;
});
html += '</div>' ;
}
// 2. Popular & Trending searches section
html +=
'<div class="yaktube-modal-section-title">🔥 Hı zlı ve Popüler Aramalar</div>' +
'<div class="yaktube-modal-chips-grid">' ;
popularTrendingQueries . forEach ( function ( item ) {
var safeQ = item . q . replace ( /"/g , '"' );
html +=
'<button class="yaktube-modal-chip" data-query="' +
safeQ +
'" aria-label="Popüler arama: ' +
safeQ +
'">' +
item . title +
'</button>' ;
});
html += '</div>' ;
container . innerHTML = html ;
// Attach click events
container . querySelectorAll ( '.yaktube-modal-chip' ). forEach ( function ( chip ) {
chip . addEventListener ( 'click' , function () {
var query = chip . getAttribute ( 'data-query' );
var inp = modal . querySelector ( '#yaktube-modal-input' );
if ( inp ) inp . value = query ;
if ( typeof window . __yaktube_execute_search__ === 'function' ) {
window . __yaktube_execute_search__ ( query );
}
});
});
var clearBtn = container . querySelector ( '#yaktube-clear-recent-btn' );
if ( clearBtn ) {
clearBtn . addEventListener ( 'click' , function ( e ) {
e . preventDefault ();
e . stopPropagation ();
clearRecentSearches ();
});
}
}
2026-08-29 23:02:50 +03:00
function createFullSearchModal () {
var existingModal = document . getElementById ( 'yaktube-full-search-modal' );
2026-08-29 21:47:30 +03:00
if ( existingModal ) return existingModal ;
2026-08-29 19:22:42 +03:00
2026-08-29 21:47:30 +03:00
var modal = document . createElement ( 'div' );
2026-08-29 23:02:50 +03:00
modal . id = 'yaktube-full-search-modal' ;
modal . className = 'yaktube-full-search-modal' ;
2026-08-29 21:47:30 +03:00
modal . setAttribute ( 'role' , 'dialog' );
modal . setAttribute ( 'aria-modal' , 'true' );
2026-08-29 23:02:50 +03:00
modal . setAttribute ( 'aria-labelledby' , 'yaktube-modal-search-title' );
2026-08-29 19:22:42 +03:00
2026-08-29 21:47:30 +03:00
modal . innerHTML =
2026-08-29 23:02:50 +03:00
'<div class="yaktube-modal-search-card">' +
'<div class="yaktube-modal-search-bar">' +
'<button id="yaktube-modal-back-btn" class="yaktube-modal-back-btn" aria-label="Aramadan Çık (Escape)" title="Aramadan Çık (Escape)">←</button>' +
'<div class="yaktube-modal-input-wrap">' +
2026-08-29 23:06:17 +03:00
'<input id="yaktube-modal-input" type="search" placeholder="Video veya kanal ara..." aria-label="Arama Metni">' +
2026-08-29 23:02:50 +03:00
'<button id="yaktube-modal-clear-btn" class="yaktube-modal-clear-btn" aria-label="Metni Temizle">✕</button>' +
2026-08-29 21:47:30 +03:00
'</div>' +
2026-08-29 23:02:50 +03:00
'<button id="yaktube-modal-submit-btn" class="yaktube-modal-search-action-btn" aria-label="Ara">🔍 Ara</button>' +
'<button id="yaktube-modal-voice-btn" class="yaktube-modal-voice-btn" aria-label="Sesle Ara (Shift+V)" title="Sesle Ara">🎙️</button>' +
'</div>' +
'<div class="yaktube-modal-body">' +
'<div id="yaktube-modal-voice-section" class="yaktube-modal-voice-section">' +
'<div id="yaktube-modal-voice-status" class="yaktube-modal-voice-status">🎙️ Dinleniyor...</div>' +
'<div id="yaktube-modal-voice-transcript" class="yaktube-modal-voice-transcript">Lütfen aramak istediğiniz videoyu söyleyin...</div>' +
'<button id="yaktube-modal-mic-pulse" class="yaktube-modal-mic-pulse-btn" aria-label="Mikrofonu durdur">🎙️</button>' +
'</div>' +
'<div class="yaktube-modal-section-title">🔥 Hı zlı ve Popüler Aramalar</div>' +
'<div class="yaktube-modal-chips-grid">' +
'<button class="yaktube-modal-chip" data-query="Tarkan">🎵 Tarkan</button>' +
'<button class="yaktube-modal-chip" data-query="Barış Manço">🎸 Barış Manço</button>' +
'<button class="yaktube-modal-chip" data-query="Python Dersleri">💻 Python Dersleri</button>' +
'<button class="yaktube-modal-chip" data-query="Yapay Zeka">🤖 Yapay Zeka</button>' +
'<button class="yaktube-modal-chip" data-query="Sesli Kitap">🎧 Sesli Kitap</button>' +
'<button class="yaktube-modal-chip" data-query="Canlı Haber">📺 Canlı Haber</button>' +
'<button class="yaktube-modal-chip" data-query="Gitar Dersi">🎼 Gitar Dersi</button>' +
'<button class="yaktube-modal-chip" data-query="Bilim Belgeseli">🌍 Bilim Belgeseli</button>' +
'</div>' +
'<div class="yaktube-modal-hint-footer">' +
'<span>💡 <b>Enter</b> ile ara, <b>Shift+V</b> ile sesli ara</span>' +
'<span><b>Escape</b> ile kapat</span>' +
2026-08-29 21:47:30 +03:00
'</div>' +
'</div>' +
'</div>' ;
2026-08-29 19:22:42 +03:00
2026-08-29 21:47:30 +03:00
document . body . appendChild ( modal );
2026-08-29 23:02:50 +03:00
var inp = modal . querySelector ( '#yaktube-modal-input' );
var clearBtn = modal . querySelector ( '#yaktube-modal-clear-btn' );
var backBtn = modal . querySelector ( '#yaktube-modal-back-btn' );
var submitBtn = modal . querySelector ( '#yaktube-modal-submit-btn' );
var voiceBtn = modal . querySelector ( '#yaktube-modal-voice-btn' );
var micPulseBtn = modal . querySelector ( '#yaktube-modal-mic-pulse' );
2026-08-29 19:22:42 +03:00
2026-08-29 23:02:50 +03:00
// Input typing & clear
inp . addEventListener ( 'input' , function () {
if ( inp . value && inp . value . trim (). length > 0 ) {
clearBtn . style . display = 'inline-flex' ;
} else {
clearBtn . style . display = 'none' ;
2026-08-29 21:47:30 +03:00
}
});
2026-08-29 23:02:50 +03:00
clearBtn . addEventListener ( 'click' , function () {
inp . value = '' ;
clearBtn . style . display = 'none' ;
inp . focus ();
announce ( 'Arama metni temizlendi.' );
});
// Close on Back button or Escape
2026-08-29 23:09:14 +03:00
backBtn . addEventListener ( 'click' , function ( e ) {
e . preventDefault ();
e . stopPropagation ();
2026-08-29 23:02:50 +03:00
closeFullSearchModal ();
});
modal . addEventListener ( 'click' , function ( e ) {
if ( e . target === modal ) {
closeFullSearchModal ();
}
});
// Search execute
function executeSearch ( query ) {
var q = ( query || inp . value || '' ). trim ();
if ( q ) {
2026-08-31 14:43:22 +03:00
saveRecentSearch ( q );
2026-08-29 23:02:50 +03:00
announce ( '"' + q + '" için arama yapı lı yor...' , true );
closeFullSearchModal ();
window . location . href = '/search/videos?search=' + encodeURIComponent ( q );
}
2026-08-29 21:47:30 +03:00
}
2026-08-31 14:43:22 +03:00
window . __yaktube_execute_search__ = executeSearch ;
renderDynamicSearchChips ();
2026-08-29 21:47:30 +03:00
2026-08-29 23:02:50 +03:00
submitBtn . addEventListener ( 'click' , function () {
executeSearch ();
});
inp . addEventListener ( 'keydown' , function ( e ) {
if ( e . key === 'Enter' ) {
e . preventDefault ();
executeSearch ();
} else if ( e . key === 'Escape' ) {
e . preventDefault ();
closeFullSearchModal ();
}
});
// Chips click
modal . querySelectorAll ( '.yaktube-modal-chip' ). forEach ( function ( chip ) {
chip . addEventListener ( 'click' , function () {
var query = chip . getAttribute ( 'data-query' );
executeSearch ( query );
});
});
// Voice button click
voiceBtn . addEventListener ( 'click' , function () {
toggleVoiceInsideModal ();
});
micPulseBtn . addEventListener ( 'click' , function () {
toggleVoiceInsideModal ();
});
2026-08-29 21:47:30 +03:00
return modal ;
}
2026-08-29 23:02:50 +03:00
function openFullSearchModal ( initialQuery , autoStartVoice ) {
var modal = createFullSearchModal ();
2026-08-29 23:09:14 +03:00
modal . classList . add ( 'yaktube-modal-open' );
2026-08-29 21:47:30 +03:00
modal . style . display = 'flex' ;
2026-08-31 14:43:22 +03:00
renderDynamicSearchChips ();
2026-08-29 23:01:08 +03:00
2026-08-29 23:02:50 +03:00
var inp = modal . querySelector ( '#yaktube-modal-input' );
var clearBtn = modal . querySelector ( '#yaktube-modal-clear-btn' );
if ( inp ) {
inp . value = initialQuery || '' ;
if ( inp . value ) clearBtn . style . display = 'inline-flex' ;
2026-08-29 23:04:50 +03:00
setTimeout ( function () {
inp . focus ();
inp . select ();
}, 50 );
2026-08-29 23:02:50 +03:00
}
2026-08-29 23:01:08 +03:00
2026-08-29 23:02:50 +03:00
announce ( 'Arama penceresi açıldı . Arayacağını z konuyu yazı n veya sesle arayı n.' , true );
2026-08-29 21:47:30 +03:00
2026-08-29 23:02:50 +03:00
if ( autoStartVoice ) {
startVoiceInsideModal ();
}
}
function closeFullSearchModal () {
var modal = document . getElementById ( 'yaktube-full-search-modal' );
2026-08-29 23:09:14 +03:00
if ( modal ) {
modal . classList . remove ( 'yaktube-modal-open' );
modal . style . display = 'none' ;
}
2026-08-29 23:02:50 +03:00
stopVoiceInsideModal ();
announce ( 'Arama penceresi kapatı ldı .' );
2026-08-29 23:09:14 +03:00
var trigger = document . querySelector ( '.yaktube-search-trigger-pill' );
if ( trigger ) {
try {
trigger . focus ();
} catch ( e ) {}
}
2026-08-29 23:02:50 +03:00
}
function toggleVoiceInsideModal () {
if ( isVoiceListening ) {
stopVoiceInsideModal ();
} else {
startVoiceInsideModal ();
}
}
function startVoiceInsideModal () {
2026-08-29 23:01:08 +03:00
var SpeechRec = window . SpeechRecognition || window . webkitSpeechRecognition ;
2026-08-29 23:02:50 +03:00
var modal = document . getElementById ( 'yaktube-full-search-modal' );
if ( ! modal ) return ;
var voiceSec = modal . querySelector ( '#yaktube-modal-voice-section' );
var statusText = modal . querySelector ( '#yaktube-modal-voice-status' );
var transcriptEl = modal . querySelector ( '#yaktube-modal-voice-transcript' );
var inp = modal . querySelector ( '#yaktube-modal-input' );
if ( voiceSec ) voiceSec . style . display = 'block' ;
if ( statusText ) statusText . textContent = '🎙️ Dinleniyor...' ;
if ( transcriptEl ) transcriptEl . textContent = 'Lütfen aramak istediğiniz videoyu söyleyin...' ;
2026-08-29 23:01:08 +03:00
if ( ! SpeechRec ) {
if ( statusText ) statusText . textContent = 'Tarayı cı da Ses Tanı ma Desteklenmiyor' ;
2026-08-29 23:02:50 +03:00
if ( transcriptEl ) transcriptEl . textContent = 'Lütfen Google Chrome veya Microsoft Edge kullanı n.' ;
announce ( 'Tarayı cı nı z ses tanı mayı desteklemiyor. Lütfen Chrome veya Edge kullanı n.' );
2026-08-29 23:01:08 +03:00
return ;
}
2026-08-29 23:02:50 +03:00
announce ( 'Sesli arama dinleniyor, lütfen aramak istediğiniz videoyu söyleyin.' , true );
2026-08-29 21:47:30 +03:00
try {
2026-08-29 23:02:50 +03:00
activeVoiceRec = new SpeechRec ();
activeVoiceRec . lang = navigator . language || 'tr-TR' ;
activeVoiceRec . continuous = false ;
activeVoiceRec . interimResults = true ;
activeVoiceRec . maxAlternatives = 1 ;
2026-08-29 21:47:30 +03:00
2026-08-29 23:02:50 +03:00
isVoiceListening = true ;
2026-08-29 21:47:30 +03:00
2026-08-29 23:02:50 +03:00
activeVoiceRec . onresult = function ( ev ) {
2026-08-29 21:47:30 +03:00
if ( ev . results && ev . results [ 0 ]) {
var transcript = ev . results [ 0 ][ 0 ]. transcript . trim ();
2026-08-29 23:02:50 +03:00
if ( transcriptEl ) transcriptEl . textContent = '"' + transcript + '"' ;
if ( inp ) inp . value = transcript ;
2026-08-29 21:47:30 +03:00
if ( ev . results [ 0 ]. isFinal ) {
announce ( '"' + transcript + '" için arama başlatı lı yor...' , true );
2026-08-29 23:02:50 +03:00
setTimeout ( function () {
closeFullSearchModal ();
window . location . href = '/search/videos?search=' + encodeURIComponent ( transcript );
}, 500 );
2026-08-29 21:47:30 +03:00
}
}
};
2026-08-29 23:02:50 +03:00
activeVoiceRec . onerror = function ( ev ) {
if ( statusText ) statusText . textContent = 'Ses Algı lanamadı ' ;
if ( transcriptEl ) transcriptEl . textContent = 'Tekrar denemek için mikrofona dokunun.' ;
stopVoiceInsideModal ();
2026-08-29 21:47:30 +03:00
};
2026-08-29 23:02:50 +03:00
activeVoiceRec . onend = function () {
stopVoiceInsideModal ();
2026-08-29 21:47:30 +03:00
};
2026-08-29 23:02:50 +03:00
activeVoiceRec . start ();
2026-08-29 21:47:30 +03:00
} catch ( err ) {
2026-08-29 23:02:50 +03:00
stopVoiceInsideModal ();
2026-08-29 21:47:30 +03:00
}
}
2026-08-29 23:02:50 +03:00
function stopVoiceInsideModal () {
isVoiceListening = false ;
if ( activeVoiceRec ) {
2026-08-29 21:47:30 +03:00
try {
2026-08-29 23:02:50 +03:00
activeVoiceRec . stop ();
2026-08-29 21:47:30 +03:00
} catch ( e ) {}
2026-08-29 23:02:50 +03:00
activeVoiceRec = null ;
2026-08-29 21:47:30 +03:00
}
2026-08-29 23:02:50 +03:00
var modal = document . getElementById ( 'yaktube-full-search-modal' );
2026-08-29 21:47:30 +03:00
if ( modal ) {
2026-08-29 23:02:50 +03:00
var voiceSec = modal . querySelector ( '#yaktube-modal-voice-section' );
if ( voiceSec ) voiceSec . style . display = 'none' ;
2026-08-29 21:47:30 +03:00
}
}
function transformToYouTubeSearchBar ( searchInp , searchContainer ) {
2026-08-29 23:04:50 +03:00
if ( searchContainer . hasAttribute ( 'data-yaktube-trigger-styled' )) return ;
searchContainer . setAttribute ( 'data-yaktube-trigger-styled' , 'true' );
2026-08-29 23:06:17 +03:00
// Hide original native inputs
2026-08-29 23:04:50 +03:00
searchInp . style . display = 'none' ;
searchInp . setAttribute ( 'tabindex' , '-1' );
searchInp . setAttribute ( 'aria-hidden' , 'true' );
var nativeInputs = searchContainer . querySelectorAll ( 'input, button' );
nativeInputs . forEach ( function ( el ) {
if ( ! el . classList . contains ( 'yaktube-search-trigger-pill' ) && ! el . classList . contains ( 'yaktube-voice-search-btn' )) {
el . style . display = 'none' ;
el . setAttribute ( 'tabindex' , '-1' );
el . setAttribute ( 'aria-hidden' , 'true' );
}
});
2026-08-29 21:47:30 +03:00
2026-08-29 22:59:18 +03:00
// 1. Create Outer Wrapper
var outerWrap = searchContainer . querySelector ( '.yaktube-search-outer-wrap' );
if ( ! outerWrap ) {
outerWrap = document . createElement ( 'div' );
outerWrap . className = 'yaktube-search-outer-wrap' ;
2026-08-29 23:04:50 +03:00
searchContainer . appendChild ( outerWrap );
2026-08-29 21:47:30 +03:00
}
2026-08-29 23:04:50 +03:00
// 2. Create Single Sleek Pill Trigger
var triggerPill = outerWrap . querySelector ( '.yaktube-search-trigger-pill' );
if ( ! triggerPill ) {
triggerPill = document . createElement ( 'button' );
triggerPill . type = 'button' ;
triggerPill . className = 'yaktube-search-trigger-pill' ;
2026-08-29 23:06:17 +03:00
triggerPill . setAttribute ( 'aria-label' , 'Ara' );
triggerPill . setAttribute ( 'title' , 'Arama Yap (/)' );
2026-08-29 21:47:30 +03:00
2026-08-29 23:04:50 +03:00
triggerPill . innerHTML =
'<span class="yaktube-search-trigger-icon">🔍</span>' +
2026-08-29 23:06:17 +03:00
'<span class="yaktube-search-trigger-text">Ara...</span>' +
2026-08-29 23:04:50 +03:00
'<span class="yaktube-search-trigger-shortcut">/</span>' ;
2026-08-29 21:47:30 +03:00
2026-08-29 23:04:50 +03:00
outerWrap . appendChild ( triggerPill );
2026-08-29 22:59:18 +03:00
2026-08-29 23:04:50 +03:00
triggerPill . addEventListener ( 'click' , function ( e ) {
2026-08-29 19:22:42 +03:00
e . preventDefault ();
2026-08-29 23:04:50 +03:00
openFullSearchModal ( '' , false );
2026-08-29 22:59:18 +03:00
});
2026-08-29 19:22:42 +03:00
}
2026-08-29 22:59:18 +03:00
// 3. YouTube Circular Voice Button (🎙️) Beside the Pill Box
var voiceBtn = outerWrap . querySelector ( '.yaktube-voice-search-btn' );
2026-08-29 21:47:30 +03:00
if ( ! voiceBtn ) {
voiceBtn = document . createElement ( 'button' );
voiceBtn . type = 'button' ;
voiceBtn . className = 'yaktube-voice-search-btn' ;
voiceBtn . setAttribute ( 'aria-label' , 'Sesle Ara (Shift+V)' );
voiceBtn . setAttribute ( 'title' , 'Sesle Ara (Shift+V)' );
voiceBtn . innerHTML = '🎙️' ;
2026-08-29 22:59:18 +03:00
outerWrap . appendChild ( voiceBtn );
2026-08-29 21:47:30 +03:00
voiceBtn . addEventListener ( 'click' , function ( e ) {
e . preventDefault ();
e . stopPropagation ();
2026-08-29 23:02:50 +03:00
openFullSearchModal ( '' , true );
2026-08-29 21:47:30 +03:00
});
2026-08-29 19:22:42 +03:00
}
}
function injectSearchEnhancements () {
var searchContainers = document . querySelectorAll (
'my-search-typeahead, .search-container, .header-search, my-header .search'
);
searchContainers . forEach ( function ( container ) {
var searchInp = container . querySelector ( 'input[type="search"], input[type="text"], input' );
2026-08-29 21:47:30 +03:00
if ( searchInp ) {
transformToYouTubeSearchBar ( searchInp , container );
2026-08-29 19:22:42 +03:00
}
});
2026-08-29 22:43:55 +03:00
// Clean up repetitive card titles and strange relative date buttons
document . querySelectorAll ( 'my-video-miniature, .video-miniature' ). forEach ( function ( card ) {
var thumbLink = card . querySelector ( 'a.thumbnail-link, .video-thumbnail a, a.thumbnail' );
if ( thumbLink && ! thumbLink . hasAttribute ( 'aria-hidden' )) {
thumbLink . setAttribute ( 'aria-hidden' , 'true' );
thumbLink . setAttribute ( 'tabindex' , '-1' );
}
var dateBtn = card . querySelector ( '.video-date-line button, .video-date button, button[title*="tarih"]' );
if ( dateBtn && ! dateBtn . hasAttribute ( 'data-cleaned' )) {
dateBtn . setAttribute ( 'data-cleaned' , 'true' );
var dateText = dateBtn . textContent . trim ();
dateBtn . setAttribute ( 'aria-label' , dateText );
2026-08-29 19:22:42 +03:00
}
});
}
2026-08-29 21:47:30 +03:00
// Global Keyboard Shortcuts
2026-08-29 19:22:42 +03:00
document . addEventListener ( 'keydown' , function ( e ) {
var activeTag = document . activeElement ? document . activeElement . tagName . toLowerCase () : '' ;
var isInputActive = activeTag === 'input' || activeTag === 'textarea' ;
2026-08-29 23:02:50 +03:00
// Shift + V: Open YouTube Voice Search Modal
2026-08-29 19:22:42 +03:00
if ( e . shiftKey && ( e . key === 'V' || e . key === 'v' ) && ! isInputActive ) {
e . preventDefault ();
2026-08-29 23:02:50 +03:00
openFullSearchModal ( '' , true );
2026-08-29 21:47:30 +03:00
}
2026-08-29 23:02:50 +03:00
// Escape: Close Full Search Modal
2026-08-29 21:47:30 +03:00
else if ( e . key === 'Escape' || e . keyCode === 27 ) {
2026-08-29 23:02:50 +03:00
var modal = document . getElementById ( 'yaktube-full-search-modal' );
2026-08-29 23:09:14 +03:00
if ( modal && modal . classList . contains ( 'yaktube-modal-open' )) {
2026-08-29 21:47:30 +03:00
e . preventDefault ();
2026-08-29 23:02:50 +03:00
closeFullSearchModal ();
2026-08-29 19:22:42 +03:00
}
2026-08-29 21:47:30 +03:00
}
2026-08-29 23:02:50 +03:00
// / or S: Open Search Modal
else if (( e . key === '/' || e . key === 's' || e . key === 'S' ) && ! isInputActive && ! e . ctrlKey && ! e . metaKey ) {
2026-08-29 19:22:42 +03:00
e . preventDefault ();
2026-08-29 23:02:50 +03:00
openFullSearchModal ( '' , false );
2026-08-29 19:22:42 +03:00
}
});
2026-08-29 12:42:36 +03:00
// 7.5. Reactive Hybrid Related Videos Engine (Watch Page)
var currentActiveVideoKey = null ;
var isFetchingRelated = false ;
var relatedShowAll = false ;
var cachedRelatedResults = [];
var cachedRelatedQuery = '' ;
function renderRelatedCards ( container , results , query ) {
var count = results ? results . length : 0 ;
if ( count === 0 ) {
container . style . display = 'none' ;
return ;
}
container . style . display = 'block' ;
var limit = relatedShowAll ? count : Math . min ( count , 12 );
var displayed = results . slice ( 0 , limit );
var cardsHtml = '' ;
displayed . forEach ( function ( item ) {
var isLocal = !! item . isDownloaded ;
var statusBadge = isLocal
? '<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); margin-bottom: 4px; display: inline-block; font-size:10px; padding:2px 8px;">✅ ' +
getInstanceName () +
"'da İzle</span>"
: '<span class="yaktube-badge" style="background: linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%); margin-bottom: 4px; display: inline-block; font-size:10px; padding:2px 8px;">⚡ Canlı Keşif</span>' ;
var actionButton = isLocal
? '<a href="' +
item . localVideo . url +
'" class="yaktube-play-btn yaktube-related-play-link" style="font-size:11px; padding:6px 10px;" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' videosunu YakTube\'dan hemen izle">▶️ Hemen İzle</a>'
: '<button class="yaktube-import-btn yaktube-related-import" data-url="' +
item . url +
'" data-title="' +
item . title . replace ( /"/g , '"' ) +
'" style="font-size:11px; padding:6px 10px;" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' videosunu ' +
getInstanceName () +
'\'a indir ve izle">📥 İndir ve İzle</button>' ;
var cardLabel =
item . title +
( isLocal ? ' (' + getInstanceName () + ' kütüphanenizde mevcut)' : ' (Canlı YouTube keşfi)' ) +
', ' +
item . channel +
' kanalı , Süre: ' +
item . durationFormatted ;
cardsHtml +=
'<div class="yaktube-card" role="article" tabindex="0" style="margin-bottom:10px;" aria-label="' +
cardLabel . replace ( /"/g , '"' ) +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
item . thumbnail +
'" alt="' +
item . title . replace ( /"/g , '"' ) +
'" loading="lazy" />' +
'<span class="yaktube-duration-badge" aria-label="Süre">' +
item . durationFormatted +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
statusBadge +
'<h4 class="yaktube-video-name" title="' +
item . title +
'" style="margin:0; font-size:12px; font-weight:600; line-height:1.35;">' +
item . title +
'</h4>' +
'<div class="yaktube-channel-name" style="font-size:10px;">' +
item . channel +
'</div>' +
'</div>' +
actionButton +
'</div>' +
'</div>' ;
});
var loadMoreBtn = '' ;
if ( count > 12 ) {
if ( ! relatedShowAll ) {
loadMoreBtn =
'<button id="yaktube-related-toggle-more" class="yaktube-shortcuts-btn" style="width:100%; margin-top:12px; padding:10px; justify-content:center; font-weight:700; background:rgba(255,143,55,0.15); border-color:#ff8f37; color:#fff;" aria-label="Daha fazla önerilen video göster">' +
'➕ Daha Fazla Öneri Göster (' +
( count - 12 ) +
' video daha)' +
'</button>' ;
} else {
loadMoreBtn =
'<button id="yaktube-related-toggle-more" class="yaktube-shortcuts-btn" style="width:100%; margin-top:12px; padding:8px; justify-content:center; font-size:11px;" aria-label="Daha az öneri göster">' +
'🔼 Daha Az Göster' +
'</button>' ;
}
}
var headerCountText = count > 12 && ! relatedShowAll ? displayed . length + ' / ' + count : count ;
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">🔥 ' +
getInstanceName () +
' Akı llı Önerilenler</span>' +
'<h3 class="yaktube-title-h3" style="margin:0; font-size:1.05rem;">"' +
query +
'" ile İlgili Öneriler (' +
headerCountText +
')</h3>' +
'</div>' +
'<button id="yaktube-related-refresh-btn" class="yaktube-shortcuts-btn" aria-label="Önerileri Yenile">' +
'🔄 Yenile' +
'</button>' +
'</div>' +
'<div class="yaktube-grid" style="grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap:12px;">' +
cardsHtml +
'</div>' +
loadMoreBtn ;
var toggleBtn = document . getElementById ( 'yaktube-related-toggle-more' );
if ( toggleBtn ) {
toggleBtn . addEventListener ( 'click' , function () {
relatedShowAll = ! relatedShowAll ;
renderRelatedCards ( container , cachedRelatedResults , cachedRelatedQuery );
announce (
relatedShowAll ? 'Tüm ' + cachedRelatedResults . length + ' önerilen video açıldı .' : 'Öneriler daraltı ldı .'
);
});
}
var refreshBtn = document . getElementById ( 'yaktube-related-refresh-btn' );
if ( refreshBtn ) {
refreshBtn . addEventListener ( 'click' , function () {
currentActiveVideoKey = null ;
checkAndInjectRelatedVideos ( true );
});
}
container . querySelectorAll ( '.yaktube-related-import' ). forEach ( function ( btn ) {
btn . addEventListener ( 'click' , function ( e ) {
var videoUrl = e . currentTarget . getAttribute ( 'data-url' );
var videoTitle = e . currentTarget . getAttribute ( 'data-title' );
handleImport ( videoUrl , e . currentTarget , videoTitle );
});
});
container . querySelectorAll ( '.yaktube-related-play-link' ). forEach ( function ( link ) {
link . addEventListener ( 'click' , function () {
currentActiveVideoKey = null ;
});
});
}
function checkAndInjectRelatedVideos ( forceReload ) {
var isWatchPage = window . location . pathname . includes ( '/videos/watch/' ) || window . location . pathname . startsWith ( '/w/' );
if ( ! isWatchPage ) {
var existing = document . getElementById ( 'yaktube-hybrid-related-container' );
if ( existing ) existing . remove ();
currentActiveVideoKey = null ;
return ;
}
var titleElem =
document . querySelector ( 'h1.video-name' ) ||
document . querySelector ( '.video-info h1' ) ||
document . querySelector ( '.video-watch-heading h1' ) ||
document . querySelector ( 'h1' ) ||
document . querySelector ( '.title' );
var rawTitle = titleElem ? ( titleElem . textContent || titleElem . innerText || '' ). trim () : '' ;
if ( ! rawTitle || rawTitle . length < 3 ) {
return ;
}
var currentPath = window . location . pathname ;
var thisVideoKey = currentPath + '::' + rawTitle . toLowerCase ();
if (
! forceReload &&
thisVideoKey === currentActiveVideoKey &&
document . getElementById ( 'yaktube-hybrid-related-container' )
) {
return ;
}
if ( isFetchingRelated ) return ;
isFetchingRelated = true ;
var cleanTopic = rawTitle
. replace ( /YakTube/gi , '' )
. replace ( /[\(\[].*?[\)\]]/g , '' )
. replace ( /official video|video klip|klip|hd|4k|lyric video|audio|remix|feat\.?|ft\.?/gi , '' )
. trim ();
var words = cleanTopic . split ( /\s+/ ). filter ( function ( w ) {
return w . length > 2 ;
});
var searchQuery = words . slice ( 0 , 3 ). join ( ' ' ) || cleanTopic ;
var container = document . getElementById ( 'yaktube-hybrid-related-container' );
if ( ! container ) {
container = document . createElement ( 'div' );
container . id = 'yaktube-hybrid-related-container' ;
container . className = 'yaktube-ondemand-section' ;
container . setAttribute ( 'role' , 'region' );
container . setAttribute ( 'aria-label' , 'YakTube Akı llı Önerilen Videolar' );
}
var recVideosMount = document . querySelector ( 'my-recommended-videos, .other-videos' );
var commentsMount = document . querySelector ( '#yaktube-watch-comments-panel, my-video-comments, .video-comments' );
var videoBottomMount = document . querySelector ( '.video-bottom, .video-info' );
var watchMount = document . querySelector ( 'my-video-watch' ) || document . querySelector ( 'main' );
if ( recVideosMount ) {
if ( container . parentElement !== recVideosMount ) {
recVideosMount . prepend ( container );
}
} else if ( commentsMount ) {
if ( container . previousElementSibling !== commentsMount ) {
commentsMount . insertAdjacentElement ( 'afterend' , container );
}
} else if ( videoBottomMount ) {
if ( container . previousElementSibling !== videoBottomMount ) {
videoBottomMount . insertAdjacentElement ( 'afterend' , container );
}
} else if ( watchMount ) {
if ( container . parentElement !== watchMount ) {
watchMount . appendChild ( container );
}
} else {
isFetchingRelated = false ;
return ;
}
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">🔥 ' +
getInstanceName () +
' Akı llı Önerilenler</span>' +
'<h3 class="yaktube-title-h3" style="margin:0; font-size:1.05rem;">"' +
searchQuery +
'" için Öneriler Taranı yor...</h3>' +
'</div>' +
'</div>' +
'<div style="padding: 18px; text-align: center; color: #ff8f37;">' +
'<div class="yaktube-spinner" style="width: 30px; height: 30px; border-width: 3px;" aria-hidden="true"></div>' +
'<div style="margin-top: 10px; font-size: 13px;">YouTube ve Fediverse\'deki en alakalı videolar getiriliyor...</div>' +
'</div>' ;
announce ( searchQuery + ' için ilgili video önerileri yükleniyor.' );
fetch ( '/api-custom/youtube-search?q=' + encodeURIComponent ( searchQuery ))
. then ( function ( res ) {
return res . json ();
})
. then ( function ( data ) {
var rawResults = data . results || [];
var normalizedCurrentTitle = rawTitle . toLowerCase (). trim ();
var uniqueResults = [];
var seenIds = {};
rawResults . forEach ( function ( item ) {
if ( item && item . id && ! seenIds [ item . id ] && item . title ) {
var itemTitle = item . title . toLowerCase (). trim ();
if ( itemTitle !== normalizedCurrentTitle && ! itemTitle . includes ( normalizedCurrentTitle )) {
seenIds [ item . id ] = true ;
uniqueResults . push ( item );
}
}
});
cachedRelatedResults = uniqueResults ;
cachedRelatedQuery = searchQuery ;
currentActiveVideoKey = thisVideoKey ;
relatedShowAll = false ;
renderRelatedCards ( container , uniqueResults , searchQuery );
var initialCount = Math . min ( uniqueResults . length , 12 );
if ( uniqueResults . length === 0 ) {
announce ( searchQuery + ' için ilgili öneri bulunamadı .' );
} else {
announce (
searchQuery +
' için ' +
initialCount +
' öneri gösteriliyor' +
( uniqueResults . length > 12 ? ', toplam ' + uniqueResults . length + ' video bulundu' : '' ) +
'.'
);
}
})
. catch ( function ( err ) {
console . error ( '[YakTube] Related fetch error:' , err );
container . style . display = 'none' ;
})
. finally ( function () {
isFetchingRelated = false ;
});
}
var lastWatchUuid = null ;
function checkAndInjectWatchPageComments () {
var pathname = window . location . pathname ;
var match = pathname . match ( /\/videos\/watch\/([a-zA-Z0-9_-]+)/ ) || pathname . match ( /\/w\/([a-zA-Z0-9_-]+)/ );
if ( ! match ) {
lastWatchUuid = null ;
var existingWatchComments = document . getElementById ( 'yaktube-watch-comments-panel' );
if ( existingWatchComments ) existingWatchComments . remove ();
return ;
}
var uuid = match [ 1 ];
var existingPanel = document . getElementById ( 'yaktube-watch-comments-panel' );
if ( uuid === lastWatchUuid && existingPanel ) {
var nativeComments = document . querySelector ( 'my-video-comments, .video-comments, .comments-block, #comments' );
if ( nativeComments && existingPanel . previousElementSibling !== nativeComments ) {
nativeComments . insertAdjacentElement ( 'afterend' , existingPanel );
}
return ;
}
lastWatchUuid = uuid ;
var mountTarget =
document . querySelector ( 'my-video-comments, .video-comments, my-video-watch, .video-bottom' ) || document . body ;
if ( ! mountTarget ) return ;
fetch ( '/api-custom/video-origin/' + encodeURIComponent ( uuid ))
. then ( function ( r ) {
return r . json ();
})
. then ( function ( originData ) {
if ( ! originData || ! originData . ok || ! originData . hasYouTubeOrigin || ! originData . youtubeId ) {
var old = document . getElementById ( 'yaktube-watch-comments-panel' );
if ( old ) old . remove ();
return ;
}
var youtubeId = originData . youtubeId ;
var existing = document . getElementById ( 'yaktube-watch-comments-panel' );
if ( existing ) existing . remove ();
var panel = document . createElement ( 'div' );
panel . id = 'yaktube-watch-comments-panel' ;
panel . className = 'yaktube-comments-panel' ;
panel . setAttribute ( 'role' , 'region' );
panel . setAttribute ( 'aria-label' , 'YouTube Orijinal Yorumlar\u0131' );
panel . style . cssText =
'margin-top: 24px; margin-bottom: 24px; background: #0f172a; border-radius: 12px; padding: 20px; border: 1px solid rgba(255,255,255,0.08); box-shadow: 0 8px 24px rgba(0,0,0,0.4);' ;
panel . innerHTML =
'<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;">' +
'<div>' +
'<h2 style="margin: 0; font-size: 16px; font-weight: 700; color: #fff; display: flex; align-items: center; gap: 8px;">' +
"\ud83d\udcac YouTube'daki Orijinal Yorumlar" +
'</h2>' +
'<div style="color: #94a3b8; font-size: 12px; margin-top: 4px;">Bu video YouTube\'dan aktar\u0131lm\u0131\u015ft\u0131r. Orijinal topluluk yorumlar\u0131n\u0131 a\u015fa\u011f\u0131dan anl\u0131k olarak y\u00fckleyebilirsiniz.</div>' +
'</div>' +
'<button id="yaktube-watch-toggle-comments-btn" style="background: #ff8f37; color: #000; border: none; padding: 8px 18px; border-radius: 8px; font-weight: 700; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; box-shadow: 0 4px 12px rgba(255,143,55,0.3);" aria-label="YouTube yorumlar\u0131n\u0131 y\u00fckle ve g\u00f6ster">' +
'\ud83d\udcac Yorumlar\u0131 G\u00f6ster (20 Yorum)' +
'</button>' +
'</div>' +
'<div id="yaktube-watch-comments-list" style="display: none; margin-top: 16px; flex-direction: column; gap: 12px;"></div>' +
'<div id="yaktube-watch-more-comments-wrap" style="text-align: center; margin-top: 18px; display: none;">' +
'<button id="yaktube-watch-more-comments-btn" style="background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 10px 24px; border-radius: 8px; font-weight: 700; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; transition: all 0.2s ease;" aria-label="Daha fazla YouTube yorumu y\u00fckle">' +
'\u2795 Daha Fazla Yorum G\u00f6ster (+20 Yorum)' +
'</button>' +
'</div>' ;
var nativeComments = document . querySelector ( 'my-video-comments, .video-comments, .comments-block, #comments' );
if ( nativeComments && nativeComments . parentElement ) {
nativeComments . insertAdjacentElement ( 'afterend' , panel );
} else {
var watchCol = document . querySelector ( '.video-info, .video-bottom, my-video-watch, .main-col' );
if ( watchCol ) {
watchCol . appendChild ( panel );
} else {
mountTarget . appendChild ( panel );
}
}
var watchCommentToken = null ;
var listContainer = document . getElementById ( 'yaktube-watch-comments-list' );
var toggleBtn = document . getElementById ( 'yaktube-watch-toggle-comments-btn' );
var moreWrap = document . getElementById ( 'yaktube-watch-more-comments-wrap' );
var moreBtn = document . getElementById ( 'yaktube-watch-more-comments-btn' );
function fetchWatchComments ( token ) {
if ( ! token ) {
toggleBtn . disabled = true ;
toggleBtn . textContent = '⏳ Yorumlar Y\u00fckleniyor...' ;
} else {
moreBtn . disabled = true ;
moreBtn . textContent = '⏳ Y\u00fckleniyor...' ;
}
var fUrl =
'/api-custom/youtube-comments?id=' +
encodeURIComponent ( youtubeId ) +
( token ? '&token=' + encodeURIComponent ( token ) : '' );
fetch ( fUrl )
. then ( function ( r ) {
return r . json ();
})
. then ( function ( res ) {
if ( ! token ) {
listContainer . innerHTML = '' ;
listContainer . style . display = 'flex' ;
toggleBtn . textContent = '🔄 Yorumlar\u0131 Yenile' ;
toggleBtn . disabled = false ;
} else {
moreBtn . textContent = '➕ Daha Fazla Yorum G\u00f6ster (+20 Yorum)' ;
moreBtn . disabled = false ;
}
var cList = res . comments || [];
if ( cList . length === 0 && ! token ) {
listContainer . innerHTML =
'<div style="color: #94a3b8; font-size: 13px; text-align: center; padding: 12px;">Bu videoda hen\u00fcz YouTube yorumu bulunmuyor.</div>' ;
moreWrap . style . display = 'none' ;
announce ( 'Bu videoda henüz YouTube yorumu bulunmuyor.' );
return ;
}
cList . forEach ( function ( c ) {
var card = document . createElement ( 'div' );
card . className = 'yaktube-comment-item' ;
card . setAttribute ( 'role' , 'article' );
card . setAttribute ( 'tabindex' , '0' );
card . setAttribute ( 'aria-label' , c . author + ' isimli kullan\u0131c\u0131n\u0131n yorumu: ' + c . content );
card . style . cssText =
'background: #1e293b; padding: 14px 16px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.05); display: flex; gap: 12px; align-items: flex-start;' ;
var avatarLetter = ( c . author || 'U' ). replace ( '@' , '' ). charAt ( 0 ). toUpperCase ();
var avatarHtml =
'<div style="width: 38px; height: 38px; border-radius: 50%; background: #334155; color: #ff8f37; font-weight: 700; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0;">' +
avatarLetter +
'</div>' ;
var safeContent = c . content
. replace ( /&/g , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( /\n/g , '<br>' );
card . innerHTML =
avatarHtml +
'<div style="flex-grow: 1; min-width: 0;">' +
'<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap;">' +
'<h3 class="yaktube-comment-author-heading" style="margin: 0; font-size: 13px; font-weight: 700; color: #f1f5f9; display: inline-block;">' +
c . author +
'</h3>' +
'<span style="color: #64748b; font-size: 11px;">' +
c . published +
'</span>' +
( c . likeCount && c . likeCount !== '0'
? '<span style="background: rgba(255,255,255,0.08); color: #94a3b8; font-size: 11px; padding: 2px 6px; border-radius: 4px; display: inline-flex; align-items: center; gap: 4px;">👍 ' +
c . likeCount +
'</span>'
: '' ) +
'</div>' +
'<div style="color: #cbd5e1; font-size: 13px; line-height: 1.5; word-break: break-word;">' +
safeContent +
'</div>' +
'</div>' ;
listContainer . appendChild ( card );
});
watchCommentToken = res . nextToken ;
if ( watchCommentToken ) {
moreWrap . style . display = 'block' ;
} else {
moreWrap . style . display = 'none' ;
}
announce ( cList . length + ' adet YouTube yorumu y\u00fcklendi.' );
})
. catch ( function ( err ) {
if ( ! token ) {
toggleBtn . disabled = false ;
toggleBtn . textContent = '⚠️ Tekrar Dene' ;
} else {
moreBtn . disabled = false ;
moreBtn . textContent = '⚠️ Tekrar Dene' ;
}
alert ( 'Yorumlar y\u00fcklenirken hata olu\u015ftu: ' + err . message );
});
}
if ( toggleBtn ) {
toggleBtn . addEventListener ( 'click' , function () {
fetchWatchComments ( null );
});
}
if ( moreBtn ) {
moreBtn . addEventListener ( 'click' , function () {
if ( watchCommentToken ) fetchWatchComments ( watchCommentToken );
});
}
})
. catch ( function () {});
}
// 6. PWA Mobile App Install Engine & Standalone Detection
var isInsideYakTubeApp =
navigator . userAgent . indexOf ( 'YakTubeMobileApp' ) !== - 1 ||
window . matchMedia ( '(display-mode: standalone)' ). matches ||
window . navigator . standalone === true ;
var isMobileDevice = /Android|iPhone|iPad|iPod|Mobile|Tablet/i . test ( navigator . userAgent );
var deferredInstallPrompt = null ;
window . addEventListener ( 'beforeinstallprompt' , function ( e ) {
if ( isInsideYakTubeApp ) return ;
e . preventDefault ();
deferredInstallPrompt = e ;
injectPWAInstallButton ();
});
function injectPWAInstallButton () {
if ( isInsideYakTubeApp ) {
var existBtn = document . getElementById ( 'yaktube-pwa-install-btn' );
if ( existBtn ) {
existBtn . setAttribute ( 'aria-hidden' , 'true' );
existBtn . remove ();
}
return ;
}
if ( ! deferredInstallPrompt || document . getElementById ( 'yaktube-pwa-install-btn' )) return ;
var headerButtons =
document . querySelector ( 'my-header .buttons-container' ) ||
document . querySelector ( '.buttons-container' ) ||
document . querySelector ( 'my-header' );
if ( ! headerButtons ) return ;
var installBtn = document . createElement ( 'button' );
installBtn . id = 'yaktube-pwa-install-btn' ;
installBtn . className = 'yaktube-pwa-btn' ;
installBtn . setAttribute ( 'type' , 'button' );
installBtn . setAttribute ( 'aria-label' , 'YakTube Mobil Uygulaması nı Yükle' );
installBtn . style . cssText =
'background: linear-gradient(135deg, #ff8f37, #f97316); color: #000; border: none; padding: 6px 14px; border-radius: 20px; font-weight: 700; font-size: 12px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; margin-right: 8px; box-shadow: 0 4px 12px rgba(255,143,55,0.4);' ;
installBtn . innerHTML = '📲 Uygulamayı Yükle' ;
installBtn . addEventListener ( 'click' , function () {
if ( deferredInstallPrompt ) {
deferredInstallPrompt . prompt ();
deferredInstallPrompt . userChoice . then ( function ( choiceResult ) {
if ( choiceResult . outcome === 'accepted' ) {
installBtn . remove ();
announce ( 'YakTube uygulaması cihazı nı za yükleniyor.' );
}
deferredInstallPrompt = null ;
});
}
});
headerButtons . prepend ( installBtn );
}
2026-08-31 17:04:44 +03:00
function isYakTubeInstance () {
return window . location . hostname . includes ( 'yaktube.yakhub.com.tr' ) || getInstanceName () === 'YakTube' ;
}
2026-08-29 12:42:36 +03:00
function injectApkDownloadButton () {
2026-08-31 17:04:44 +03:00
if ( ! isYakTubeInstance ()) return ;
2026-08-29 12:42:36 +03:00
// 1. If inside APK: NEVER show APK button
if ( isInsideYakTubeApp ) {
var existBtnApp = document . getElementById ( 'yaktube-apk-download-btn' );
if ( existBtnApp ) {
existBtnApp . setAttribute ( 'aria-hidden' , 'true' );
existBtnApp . remove ();
}
return ;
}
// 2. If on Desktop: NEVER show in top header (keep header clean)
if ( ! isMobileDevice ) {
var existBtnDesktop = document . getElementById ( 'yaktube-apk-download-btn' );
if ( existBtnDesktop ) {
existBtnDesktop . setAttribute ( 'aria-hidden' , 'true' );
existBtnDesktop . remove ();
}
return ;
}
// 3. If on Mobile Web: Show in header
if ( document . getElementById ( 'yaktube-apk-download-btn' )) return ;
var headerButtons =
document . querySelector ( 'my-header .buttons-container' ) ||
document . querySelector ( 'my-header .header-right' ) ||
document . querySelector ( '.buttons-container' ) ||
document . querySelector ( 'my-header' );
if ( ! headerButtons ) return ;
var apkBtn = document . createElement ( 'a' );
apkBtn . id = 'yaktube-apk-download-btn' ;
apkBtn . href = '/yaktube.apk' ;
apkBtn . className = 'yaktube-apk-btn' ;
apkBtn . setAttribute ( 'download' , 'YakTube_Mobile_v1.0.apk' );
apkBtn . setAttribute ( 'aria-label' , 'YakTube Android APK İndir (4.5 MB)' );
apkBtn . style . cssText =
'background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 6px 12px; border-radius: 20px; font-weight: 700; font-size: 11px; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; margin-right: 6px; transition: all 0.2s; box-shadow: 0 4px 12px rgba(0,0,0,0.3);' ;
apkBtn . innerHTML = '🤖 APK İndir (4.5 MB)' ;
headerButtons . prepend ( apkBtn );
}
function injectSidebarApkLink () {
2026-08-31 17:04:44 +03:00
if ( ! isYakTubeInstance ()) return ;
2026-08-29 12:42:36 +03:00
// If inside APK: NEVER show in sidebar
if ( isInsideYakTubeApp ) {
var existLink = document . getElementById ( 'yaktube-sidebar-apk-link' );
if ( existLink ) {
existLink . setAttribute ( 'aria-hidden' , 'true' );
existLink . remove ();
}
return ;
}
if ( document . getElementById ( 'yaktube-sidebar-apk-link' )) return ;
var menuContainer = document . querySelector ( 'my-menu, .menu, .main-menu, .nav-list' );
if ( ! menuContainer ) return ;
var sidebarLink = document . createElement ( 'a' );
sidebarLink . id = 'yaktube-sidebar-apk-link' ;
sidebarLink . href = '/yaktube.apk' ;
sidebarLink . className = 'menu-link item-link' ;
sidebarLink . setAttribute ( 'download' , 'YakTube_Mobile_v1.0.apk' );
sidebarLink . setAttribute ( 'aria-label' , 'YakTube Android Uygulaması nı İndir (4.5 MB)' );
sidebarLink . style . cssText =
'display: flex; align-items: center; gap: 10px; padding: 10px 16px; margin: 8px 12px; background: rgba(255, 143, 55, 0.1); color: #ff8f37; border: 1px solid rgba(255, 143, 55, 0.3); border-radius: 8px; font-weight: 700; font-size: 13px; text-decoration: none;' ;
sidebarLink . innerHTML = '<span>🤖</span> <span>YakTube Android APK (4.5 MB)</span>' ;
menuContainer . appendChild ( sidebarLink );
}
setInterval ( function () {
checkAndInjectSearchResults ();
2026-08-29 19:22:42 +03:00
injectSearchEnhancements ();
2026-08-29 12:42:36 +03:00
checkAndInjectRelatedVideos ( false );
restoreActiveLiveStream ();
checkAndInjectWatchPageComments ();
injectPWAInstallButton ();
injectApkDownloadButton ();
injectSidebarApkLink ();
}, 800 );
restoreActiveLiveStream ();
window . addEventListener ( 'popstate' , function () {
currentActiveVideoKey = null ;
checkAndInjectSearchResults ();
checkAndInjectRelatedVideos ( true );
checkAndInjectWatchPageComments ();
});
checkAndInjectWatchPageComments ();
})();
2026-08-31 16:41:58 +03:00
// 5. Background Play (Screen-Off / Minimize) & MediaSession Lock-Screen Controls
( function initBackgroundPlaybackEngine () {
var videoState = {
hasPlayed : false ,
pausedByUser : false ,
pausedAt : null
};
function attachVideoListeners ( video ) {
if ( ! video || video . hasAttribute ( 'data-yaktube-bgplay-hooked' )) return ;
video . setAttribute ( 'data-yaktube-bgplay-hooked' , 'true' );
video . addEventListener ( 'play' , function () {
videoState . hasPlayed = true ;
videoState . pausedByUser = false ;
updateMediaSessionMetadata ( video );
});
video . addEventListener ( 'pause' , function () {
videoState . pausedAt = Date . now ();
// If document is visible when paused, it was paused manually by the user
if ( ! document . hidden ) {
videoState . pausedByUser = true ;
}
});
video . addEventListener ( 'ended' , function () {
videoState . hasPlayed = false ;
videoState . pausedByUser = true ;
});
video . addEventListener ( 'timeupdate' , function () {
updateMediaSessionPosition ( video );
});
}
function handleVisibilityChange () {
if ( ! document . hidden ) return ; // Screen is active
var video = getVideoElement ();
if ( ! video ) return ;
// If user hasn't played or deliberately paused, do not auto-resume
if ( ! videoState . hasPlayed || videoState . pausedByUser ) return ;
var now = Date . now ();
if ( videoState . pausedAt && now - videoState . pausedAt > 1500 && videoState . pausedByUser ) {
return ;
}
// Resume playback when screen locks or tab switches
setTimeout ( function () {
if ( video . paused && ! videoState . pausedByUser ) {
video . play (). catch ( function () {});
}
}, 50 );
}
document . addEventListener ( 'visibilitychange' , handleVisibilityChange , false );
document . addEventListener ( 'webkitvisibilitychange' , handleVisibilityChange , false );
// MediaSession API Integration (Lock Screen, Bluetooth, Smart Watches)
function updateMediaSessionMetadata ( video ) {
if ( ! ( 'mediaSession' in navigator )) return ;
var titleEl = document . querySelector ( 'h1.video-title, .video-name, my-video-watch h1, .title' );
var title = titleEl ? titleEl . textContent . trim () : 'YakTube Video' ;
var authorEl = document . querySelector ( '.actor-name, .channel-name, .video-channel-name, a.actor-link' );
var artist = authorEl ? authorEl . textContent . trim () : 'YakTube' ;
var artwork = [];
var thumbEl = document . querySelector ( 'meta[property="og:image"]' );
if ( thumbEl && thumbEl . content ) {
artwork . push ({ src : thumbEl . content , sizes : '512x512' , type : 'image/jpeg' });
}
try {
navigator . mediaSession . metadata = new MediaMetadata ({
title : title ,
artist : artist ,
album : 'YakTube' ,
artwork : artwork
});
navigator . mediaSession . setActionHandler ( 'play' , function () {
video . play ();
announce ( 'Video oynatı lı yor.' );
});
navigator . mediaSession . setActionHandler ( 'pause' , function () {
videoState . pausedByUser = true ;
video . pause ();
announce ( 'Video duraklatı ldı .' );
});
navigator . mediaSession . setActionHandler ( 'seekbackward' , function ( details ) {
var skipTime = details . seekOffset || 10 ;
video . currentTime = Math . max ( 0 , video . currentTime - skipTime );
announce ( skipTime + ' saniye geri sarı ldı .' );
});
navigator . mediaSession . setActionHandler ( 'seekforward' , function ( details ) {
var skipTime = details . seekOffset || 10 ;
video . currentTime = Math . min ( video . duration || 99999 , video . currentTime + skipTime );
announce ( skipTime + ' saniye ileri sarı ldı .' );
});
navigator . mediaSession . setActionHandler ( 'stop' , function () {
videoState . pausedByUser = true ;
video . pause ();
});
} catch ( e ) {}
}
function updateMediaSessionPosition ( video ) {
if ( ! ( 'mediaSession' in navigator ) || ! navigator . mediaSession . setPositionState ) return ;
if ( isNaN ( video . duration ) || isNaN ( video . currentTime ) || video . duration <= 0 ) return ;
try {
navigator . mediaSession . setPositionState ({
duration : video . duration ,
playbackRate : video . playbackRate || 1 ,
position : Math . min ( video . currentTime , video . duration )
});
} catch ( e ) {}
}
// Auto-hook active video elements
setInterval ( function () {
var video = getVideoElement ();
if ( video ) attachVideoListeners ( video );
}, 1000 );
})();