Files
peertube-plugin-yaktube-ond…/client.js
T

4528 lines
190 KiB
JavaScript
Raw Normal View History

2026-09-04 12:54:11 +03:00
// 100% Incompatible Browser Suppression & Error Guardian
// CRITICAL: PeerTube's Angular app (my-app ngOnInit) relies on document.getElementById('incompatible-browser')
// to exist so it can do .className += " browser-ok" before calling this.loadUser()!
// Removing it from DOM causes a null pointer crash that breaks user session initialization!
if (typeof window !== 'undefined') {
try {
window.displayIncompatibleBrowser = function () {};
var neuterIncompatible = function () {
var el = document.getElementById('incompatible-browser');
if (el) {
if (!el.classList.contains('browser-ok')) {
el.classList.add('browser-ok');
}
2026-09-04 12:54:11 +03:00
el.style.setProperty('display', 'none', 'important');
el.style.setProperty('visibility', 'hidden', 'important');
el.style.setProperty('height', '0px', 'important');
el.style.setProperty('width', '0px', 'important');
el.style.setProperty('opacity', '0', 'important');
el.style.setProperty('pointer-events', 'none', 'important');
} else {
try {
var dummy = document.createElement('div');
dummy.id = 'incompatible-browser';
dummy.className = 'browser-ok';
dummy.style.display = 'none';
(document.body || document.documentElement).appendChild(dummy);
} catch (e) {}
}
2026-09-04 12:54:11 +03:00
};
neuterIncompatible();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', neuterIncompatible);
}
var neuterInterval = setInterval(neuterIncompatible, 1000);
setTimeout(function () {
clearInterval(neuterInterval);
}, 5000);
2026-09-04 12:54:11 +03:00
} catch (e) {}
}
// Immediate URL rewrite: redirect legacy /search/videos to /search
if (typeof window !== 'undefined' && window.location) {
if (window.location.pathname === '/search/videos' || window.location.pathname.startsWith('/search/videos/')) {
var targetSearch = window.location.search || '';
window.location.replace('/search' + targetSearch);
}
2026-09-04 12:54:11 +03:00
}
try {
(function () {
if (window.__YAKTUBE_ENGINE_LOADED__) return;
window.__YAKTUBE_ENGINE_LOADED__ = true;
try {
localStorage.setItem('hide-mobile-msg', 'true');
sessionStorage.setItem('hide-mobile-msg', 'true');
} catch (e) {}
console.log('[YakTube] YakNet SSO, Custom Branding, A11y & On-Demand Engine Active');
// Ensure Fediverse Global Search (SepiaSearch) is default in PeertubeServerConfig
try {
2026-09-19 05:20:33 +03:00
if (
window.PeertubeServerConfig &&
window.PeertubeServerConfig.search &&
window.PeertubeServerConfig.search.searchIndex
) {
window.PeertubeServerConfig.search.searchIndex.isDefaultSearch = true;
}
} catch (e) {}
// 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 (W3C WCAG 2.2 AAA Compliant Auto-Clearing Engine)
function applySrOnlyStyles(el) {
el.className = 'yaktube-sr-only';
el.style.position = 'absolute';
el.style.width = '1px';
el.style.height = '1px';
el.style.padding = '0';
el.style.margin = '-1px';
el.style.overflow = 'hidden';
el.style.clip = 'rect(0, 0, 0, 0)';
el.style.whiteSpace = 'nowrap';
el.style.border = '0';
}
function getLiveRegion(assertive) {
var id = assertive ? 'yaktube-a11y-live-assertive' : 'yaktube-a11y-live-region';
var region = document.getElementById(id);
if (!region || !document.body.contains(region)) {
region = document.createElement('div');
region.id = id;
region.setAttribute('aria-live', assertive ? 'assertive' : 'polite');
region.setAttribute('aria-atomic', 'true');
region.setAttribute('aria-relevant', 'additions text');
applySrOnlyStyles(region);
document.body.appendChild(region);
}
return region;
}
function announce(text, assertive) {
if (!text || !String(text).trim()) return;
var strText = String(text).trim();
var region = getLiveRegion(assertive);
if (region._setTimer) {
clearTimeout(region._setTimer);
region._setTimer = null;
}
if (region._clearTimer) {
clearTimeout(region._clearTimer);
region._clearTimer = null;
}
// Screen reader mutation trigger: clear first so repeat announcements are re-announced
region.textContent = '';
region._setTimer = setTimeout(function () {
region.textContent = strText;
// Seslendirme yapıldıktan veya ekran okuyucu kuyruğuna alındıktan sonra
// sayfanın en altında ya da sanal imleç gezintisinde eski metin kalmaması için
// DOM'daki duyuru metnini temizle (Metin uzunluğuna göre 3 ile 7 saniye arası)
var clearDelay = Math.min(7000, Math.max(3000, strText.length * 50));
region._clearTimer = setTimeout(function () {
if (region && document.body.contains(region)) {
region.textContent = '';
}
region._clearTimer = null;
}, clearDelay);
region._setTimer = null;
}, 50);
}
// Load official YakNet Web Component definition
if (
typeof customElements !== 'undefined' &&
!customElements.get('yaknet-account') &&
!document.getElementById('yaknet-account-script')
) {
var s = document.createElement('script');
s.id = 'yaknet-account-script';
s.src = 'https://auth.yakhub.com.tr/js/yaknet-account.js';
s.async = true;
document.head.appendChild(s);
}
// Intercept and sanitize YakNet Account Web Component popup menu
function hookYakNetAccountComponent() {
if (typeof customElements === 'undefined') return;
2026-09-19 05:20:33 +03:00
customElements
.whenDefined('yaknet-account')
.then(function () {
var YakNetAccount = customElements.get('yaknet-account');
if (!YakNetAccount || !YakNetAccount.prototype || YakNetAccount.prototype._yaktubeSanitizerHooked) return;
YakNetAccount.prototype._yaktubeSanitizerHooked = true;
var originalRender = YakNetAccount.prototype.render;
if (typeof originalRender === 'function') {
YakNetAccount.prototype.render = function () {
originalRender.call(this);
try {
if (this.shadowRoot) {
var isOfficial = isOfficialYakTubeServer();
// 1. In YakTube: Suppress redundant/recursive link to YakTube inside services list
var selfLinks = this.shadowRoot.querySelectorAll('a[href*="yaktube.yakhub.com.tr"]');
selfLinks.forEach(function (a) {
a.style.display = 'none';
});
// 2. In third-party PeerTube instances: Suppress entire external services grid & header to avoid community backlash
if (!isOfficial) {
var appsGrid = this.shadowRoot.querySelector('.yn-apps-grid');
if (appsGrid) appsGrid.style.display = 'none';
var sectionTitle = this.shadowRoot.querySelector('#yn-services-label, .yn-section-title');
if (sectionTitle) sectionTitle.style.display = 'none';
}
}
2026-09-19 05:20:33 +03:00
} catch (e) {}
};
}
})
.catch(function () {});
}
hookYakNetAccountComponent();
// Silent background SSO Probe Engine
function initSilentSSOProbe() {
var token = localStorage.getItem('access_token');
var isManualLogout = sessionStorage.getItem('yaknet_manual_logout');
if (
token ||
isManualLogout ||
window.location.search.includes('local=true') ||
window.location.search.includes('externalAuthError') ||
window.location.pathname.includes('/login')
) {
return;
}
var iframeId = 'yaknet-sso-iframe-instance';
if (!document.getElementById(iframeId)) {
var iframe = document.createElement('iframe');
iframe.id = iframeId;
iframe.src = 'https://auth.yakhub.com.tr/sdk/sso-frame';
iframe.title = 'YakNet Oturum Kontrolü';
iframe.setAttribute('aria-hidden', 'true');
iframe.tabIndex = -1;
iframe.style.cssText =
'display:none!important;width:0!important;height:0!important;border:0!important;position:absolute!important;pointer-events:none!important;';
document.body.appendChild(iframe);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSilentSSOProbe);
} else {
initSilentSSOProbe();
}
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...');
clearYakTubeAuthSession();
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) {
clearYakTubeAuthSession();
window.location.reload();
}
}
});
// Clear YakTube auth session and cloud search cache safely
function clearYakTubeAuthSession() {
try {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('token_type');
localStorage.removeItem('id');
localStorage.removeItem('username');
localStorage.removeItem('displayName');
localStorage.removeItem('email');
localStorage.removeItem('role');
localStorage.removeItem('yaktube_recent_searches');
sessionStorage.clear();
} catch (e) {}
}
// Unified single sign-out for YakTube and YakNet
function performUnifiedLogout() {
clearYakTubeAuthSession();
try {
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
);
// 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';
}
});
}
// 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) {
var liveWidget = headerRight.querySelector('#yaknet-header-account-widget');
if (isAuth) {
// 1. AUTHENTICATED: Add class to body & Hide ALL native login links completely
document.body.classList.add('yaktube-user-logged-in');
document
.querySelectorAll('my-login-link, a[href*="/login"], .login-button, [class*="login-link"]')
.forEach(function (btn) {
btn.style.setProperty('display', 'none', 'important');
btn.setAttribute('aria-hidden', 'true');
btn.setAttribute('tabindex', '-1');
});
if (!liveWidget && isOfficialYakTubeServer()) {
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, '&quot;') +
'" ' +
'user-email="' +
emailStr.replace(/"/g, '&quot;') +
'" ' +
'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 if (liveWidget) {
if (!isOfficialYakTubeServer()) {
liveWidget.remove();
} else {
var el = liveWidget.querySelector('yaknet-account');
if (el) {
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();
}
}
if (el.shadowRoot) {
// Direct DOM safety: remove self-link on polling cycle too
var selfLinks = el.shadowRoot.querySelectorAll('a[href*="yaktube.yakhub.com.tr"]');
selfLinks.forEach(function (a) {
a.style.display = 'none';
});
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
);
}
}
}
}
}
} else {
// 2. GUEST / NOT LOGGED IN:
document.body.classList.remove('yaktube-user-logged-in');
if (liveWidget) {
liveWidget.remove();
}
document
.querySelectorAll('my-login-link, a[href*="/login"], .login-button, [class*="login-link"]')
.forEach(function (btn) {
btn.style.display = '';
btn.removeAttribute('aria-hidden');
btn.removeAttribute('tabindex');
var loginAnchor = btn.tagName.toLowerCase() === 'a' ? btn : btn.querySelector('a');
if (loginAnchor) {
loginAnchor.setAttribute('aria-label', 'YakNet ile Giriş Yap');
loginAnchor.setAttribute('title', 'YakNet ile Giriş Yap');
var spanText = loginAnchor.querySelector('span');
if (spanText) {
spanText.textContent = 'YakNet ile Giriş Yap';
} else {
loginAnchor.textContent = 'YakNet ile Giriş Yap';
}
}
});
}
}
// 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();
openLocalAdminLoginModal();
});
}
}
}
}
// 3. Intelligent Branding & A11y DOM Auto-Fixer
function autoFixDOM() {
injectAccessiblePlayerBar();
// 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);
}
injectYakNetSSO();
// Check auth status for login button rendering
var tokenVal = localStorage.getItem('access_token');
var userVal = localStorage.getItem('username');
var isAlreadyAuth = !!tokenVal && !!userVal;
if (!isAlreadyAuth) {
// Replace all login buttons labels and titles ONLY for guests
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';
}
});
}
});
}
// Aggressively remove generic Framasoft Open-in-App / Play Store banners
document
.querySelectorAll(
'.mobile-msg, [class*="mobile-msg"], button[title*="Bu mesajı kapat"], button[aria-label*="Bu mesajı kapat"]'
)
.forEach(function (m) {
m.remove();
});
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')
.replace(/PeerTube/g, 'YakTube')
.replace(/Giriş sayfasına git/gi, 'YakNet ile Giriş Yap');
}
}
}
// 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');
}
}
});
}
// 3. Screen-Reader & Turkish Q Optimized Shortcuts & Player Bar (v1.4.1)
function toggleShortcutsModal() {
var modal = document.getElementById('yaktube-shortcuts-dialog');
if (modal) {
if (modal.style.display === 'none' || !modal.style.display) {
modal.style.display = 'flex';
announce('Klavye kısayolları yardım penceresi açıldı. Kapatmak için Escape tuşuna basın.');
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 =
'<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);">' +
'<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">' +
'<h3 id="yaktube-shortcuts-title" style="margin:0; font-size:18px; color:#ff8f37;">⌨️ Klavye Kısayolları (Ekran Okuyucu Uyumlu)</h3>' +
'<button id="yaktube-shortcuts-close" class="yaktube-voice-modal-close-btn" aria-label="Kapat">✕</button>' +
'</div>' +
'<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>' +
'<table class="yaktube-shortcuts-table" style="width:100%; border-collapse:collapse; font-size:13px;">' +
'<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>' +
'<tbody>' +
'<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 + U</span></td><td>⏱️ Uyku Zamanlayıcısı (Sleep Timer)</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + Q</span></td><td>📱 Telefonda Devam Et (QR Kod)</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>' +
'</tbody>' +
'</table>' +
'</div>';
document.body.appendChild(modal);
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';
});
announce('Klavye kısayolları yardım penceresi açıldı. Kapatmak için Escape tuşuna basın.');
}
}
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() {
// 1. Direct video tag in DOM
var v = document.querySelector('video');
if (v) return v;
// 2. Video inside iframes (e.g. embed player)
var iframes = document.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
try {
var doc = iframes[i].contentDocument || (iframes[i].contentWindow && iframes[i].contentWindow.document);
if (doc) {
var iv = doc.querySelector('video');
if (iv) return iv;
}
} catch (e) {}
}
return null;
}
window.getVideoElement = getVideoElement;
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';
}
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);
var speed = (video.playbackRate || 1) + 'x';
var volume = Math.round((video.volume || 1) * 100) + '%';
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];
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ü.');
}
}
// ==========================================================================
// YAKTUBE QUICK TOAST HELPER (v1.5.0)
// ==========================================================================
function showQuickToast(message, icon) {
var existing = document.getElementById('yaktube-quick-toast');
if (existing) existing.remove();
var toast = document.createElement('div');
toast.id = 'yaktube-quick-toast';
toast.className = 'yaktube-quick-toast';
toast.style.cssText =
'position:fixed!important;top:80px!important;left:50%!important;transform:translateX(-50%)!important;' +
'background:rgba(24,24,31,0.92)!important;border:1px solid #ff8f37!important;color:#fff!important;' +
'padding:10px 22px!important;border-radius:30px!important;font-size:14px!important;font-weight:700!important;' +
'box-shadow:0 10px 30px rgba(0,0,0,0.8)!important;z-index:999999!important;pointer-events:none!important;' +
'backdrop-filter:blur(8px)!important;display:flex!important;align-items:center!important;gap:8px!important;';
toast.innerHTML = (icon ? '<span>' + icon + '</span> ' : '') + '<span>' + message + '</span>';
document.body.appendChild(toast);
setTimeout(function () {
if (toast && toast.parentNode) {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s ease-out';
setTimeout(function () {
if (toast.parentNode) toast.parentNode.removeChild(toast);
}, 300);
}
}, 2200);
}
// ==========================================================================
// YAKTUBE SLEEP TIMER ENGINE (v1.5.0)
// ==========================================================================
var sleepTimer = {
countdownInterval: null,
targetTime: 0,
mode: null,
originalVolume: 1
};
function clearSleepTimer() {
if (sleepTimer.countdownInterval) clearInterval(sleepTimer.countdownInterval);
sleepTimer.countdownInterval = null;
sleepTimer.targetTime = 0;
sleepTimer.mode = null;
var badge = document.getElementById('yaktube-sleep-timer-badge');
if (badge) badge.remove();
var endListenerVideo = getVideoElement();
if (endListenerVideo && endListenerVideo._sleepEndListener) {
endListenerVideo.removeEventListener('ended', endListenerVideo._sleepEndListener);
endListenerVideo._sleepEndListener = null;
}
}
function setSleepTimer(modeMinutes) {
clearSleepTimer();
var video = getVideoElement();
if (!video) {
announce('Aktif video bulunamadı.');
return;
}
sleepTimer.mode = modeMinutes;
sleepTimer.originalVolume = video.volume || 1;
if (modeMinutes === 'end') {
announce('Uyku zamanlayıcısı ayarlandı: Bu video bitince durdurulacak.', true);
showQuickToast('⏱️ Uyku: Video Bitince');
video._sleepEndListener = function () {
video.pause();
announce('Uyku zamanlayıcısı: Video bitti, oynatma durduruldu.', true);
clearSleepTimer();
};
video.addEventListener('ended', video._sleepEndListener, { once: true });
updateSleepTimerBadge('Video Bitince');
return;
}
var mins = parseInt(modeMinutes, 10);
if (isNaN(mins) || mins <= 0) return;
sleepTimer.targetTime = Date.now() + mins * 60 * 1000;
announce('Uyku zamanlayıcısı ' + mins + ' dakikaya ayarlandı.', true);
showQuickToast('⏱️ Uyku: ' + mins + ' Dakika');
function updateTick() {
var remainingMs = Math.max(0, sleepTimer.targetTime - Date.now());
var remainingSec = Math.floor(remainingMs / 1000);
var m = Math.floor(remainingSec / 60);
var s = remainingSec % 60;
var display = (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
updateSleepTimerBadge(display);
// Gentle volume fadeout in last 15 seconds
if (remainingSec <= 15 && remainingSec > 0 && video && !video.paused) {
var stepRatio = remainingSec / 15;
video.volume = Math.max(0, sleepTimer.originalVolume * stepRatio);
}
if (remainingMs <= 0) {
clearSleepTimer();
if (video) {
video.pause();
video.volume = sleepTimer.originalVolume;
}
announce('Uyku zamanlayıcısı süresi doldu, video durduruldu.', true);
showQuickToast('⏱️ Uyku: Oynatma Durduruldu');
}
}
updateTick();
sleepTimer.countdownInterval = setInterval(updateTick, 1000);
}
function updateSleepTimerBadge(text) {
var bar = document.getElementById('yaktube-player-action-bar');
if (!bar) return;
var badge = document.getElementById('yaktube-sleep-timer-badge');
if (!badge) {
badge = document.createElement('span');
badge.id = 'yaktube-sleep-timer-badge';
badge.className = 'yaktube-timer-badge';
var sleepBtn = document.getElementById('yaktube-bar-sleep-btn');
if (sleepBtn && sleepBtn.nextSibling) {
bar.insertBefore(badge, sleepBtn.nextSibling);
} else {
bar.appendChild(badge);
}
}
badge.textContent = '⏱️ ' + text;
}
function openSleepTimerModal() {
var existing = document.getElementById('yaktube-sleep-modal');
if (existing) existing.remove();
var backdrop = document.createElement('div');
backdrop.id = 'yaktube-sleep-modal';
backdrop.className = 'yaktube-feature-modal-backdrop';
backdrop.setAttribute('role', 'dialog');
backdrop.setAttribute('aria-modal', 'true');
backdrop.setAttribute('aria-label', 'Uyku Zamanlayıcısı');
var card = document.createElement('div');
card.className = 'yaktube-feature-modal-card';
var header = document.createElement('div');
header.className = 'yaktube-feature-modal-header';
2026-09-19 05:20:33 +03:00
header.innerHTML =
'<h3 class="yaktube-feature-modal-title"><span>⏱️</span> Uyku Zamanlayıcısı</h3>' +
'<button class="yaktube-feature-modal-close" aria-label="Kapat (Escape)">&times;</button>';
var body = document.createElement('div');
body.className = 'yaktube-feature-modal-body';
2026-09-19 05:20:33 +03:00
body.innerHTML =
'<p class="yaktube-qr-desc">Videonun ne zaman durdurulacağını seçin. Süre bittiğinde ses yavaşça kısılarak oynatma durdurulur.</p>' +
'<div class="yaktube-timer-options">' +
2026-09-19 05:20:33 +03:00
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '15' ? ' active' : '') +
'" data-mins="15">15 Dakika</button>' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '30' ? ' active' : '') +
'" data-mins="30">30 Dakika</button>' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '45' ? ' active' : '') +
'" data-mins="45">45 Dakika</button>' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '60' ? ' active' : '') +
'" data-mins="60">60 Dakika</button>' +
'</div>' +
2026-09-19 05:20:33 +03:00
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === 'end' ? ' active' : '') +
'" data-mins="end" style="width:100%;">🎬 Bu Video Bitince</button>' +
(sleepTimer.targetTime || sleepTimer.mode
? '<button id="yaktube-timer-cancel-btn" class="yaktube-timer-btn" style="background:#ef4444!important;color:#fff!important;border-color:#ef4444!important;">❌ Zamanlayıcıyı İptal Et</button>'
: '');
card.appendChild(header);
card.appendChild(body);
backdrop.appendChild(card);
document.body.appendChild(backdrop);
function closeModal() {
backdrop.remove();
announce('Uyku zamanlayıcısı penceresi kapatıldı.');
}
header.querySelector('.yaktube-feature-modal-close').addEventListener('click', closeModal);
backdrop.addEventListener('click', function (e) {
if (e.target === backdrop) closeModal();
});
body.querySelectorAll('.yaktube-timer-options .yaktube-timer-btn, button[data-mins="end"]').forEach(function (b) {
b.addEventListener('click', function () {
var mins = this.getAttribute('data-mins');
setSleepTimer(mins);
closeModal();
});
});
var cancelBtn = body.querySelector('#yaktube-timer-cancel-btn');
if (cancelBtn) {
cancelBtn.addEventListener('click', function () {
clearSleepTimer();
announce('Uyku zamanlayıcısı iptal edildi.', true);
showQuickToast('⏱️ Zamanlayıcı İptal Edildi');
closeModal();
});
}
var firstBtn = body.querySelector('.yaktube-timer-btn');
if (firstBtn) firstBtn.focus();
announce('Uyku zamanlayıcısı penceresi açıldı. Seçeneklerden birini seçin.');
}
// ==========================================================================
// YAKTUBE PHONE TRANSFER / QR CODE ENGINE (v1.5.0)
// ==========================================================================
function openPhoneTransferModal() {
var video = getVideoElement();
var currentSec = video ? Math.floor(video.currentTime || 0) : 0;
var currentUrl = new URL(window.location.href);
if (currentSec > 0) {
currentUrl.searchParams.set('start', currentSec + 's');
}
var targetUrl = currentUrl.toString();
var existing = document.getElementById('yaktube-qr-modal');
if (existing) existing.remove();
var backdrop = document.createElement('div');
backdrop.id = 'yaktube-qr-modal';
backdrop.className = 'yaktube-feature-modal-backdrop';
backdrop.setAttribute('role', 'dialog');
backdrop.setAttribute('aria-modal', 'true');
backdrop.setAttribute('aria-label', 'Telefonda Devam Et');
var card = document.createElement('div');
card.className = 'yaktube-feature-modal-card';
var header = document.createElement('div');
header.className = 'yaktube-feature-modal-header';
2026-09-19 05:20:33 +03:00
header.innerHTML =
'<h3 class="yaktube-feature-modal-title"><span>📱</span> Telefonda Devam Et</h3>' +
'<button class="yaktube-feature-modal-close" aria-label="Kapat (Escape)">&times;</button>';
var formattedTime = formatClockTime(currentSec);
2026-09-19 05:20:33 +03:00
var qrImageUrl =
'https://api.qrserver.com/v1/create-qr-code/?size=220x220&margin=8&data=' + encodeURIComponent(targetUrl);
var body = document.createElement('div');
body.className = 'yaktube-feature-modal-body';
body.innerHTML =
2026-09-19 05:20:33 +03:00
'<p class="yaktube-qr-desc">Telefonunuzun kamerasıyla QR kodu okutarak videoyu kaldığınız yerden (<strong>' +
formattedTime +
'</strong>) izlemeye devam edebilirsiniz.</p>' +
'<div class="yaktube-qr-wrapper">' +
2026-09-19 05:20:33 +03:00
'<img src="' +
qrImageUrl +
'" alt="Video Bağlantısı QR Kodu" width="190" height="190" style="display:block;border-radius:8px;" />' +
'</div>' +
'<button id="yaktube-qr-copy-action-btn" class="yaktube-qr-copy-btn">📋 Bağlantıyı Kopyala</button>';
card.appendChild(header);
card.appendChild(body);
backdrop.appendChild(card);
document.body.appendChild(backdrop);
function closeModal() {
backdrop.remove();
announce('Telefonda devam et penceresi kapatıldı.');
}
header.querySelector('.yaktube-feature-modal-close').addEventListener('click', closeModal);
backdrop.addEventListener('click', function (e) {
if (e.target === backdrop) closeModal();
});
var copyBtn = body.querySelector('#yaktube-qr-copy-action-btn');
copyBtn.addEventListener('click', function () {
if (navigator.clipboard && navigator.clipboard.writeText) {
2026-09-19 05:20:33 +03:00
navigator.clipboard
.writeText(targetUrl)
.then(function () {
copyBtn.textContent = '✅ Bağlantı Kopyalandı!';
announce('Bağlantı panoya kopyalandı: ' + targetUrl);
setTimeout(function () {
copyBtn.textContent = '📋 Bağlantıyı Kopyala';
}, 2500);
})
.catch(function () {
fallbackCopy(targetUrl, copyBtn);
});
} else {
fallbackCopy(targetUrl, copyBtn);
}
});
function fallbackCopy(text, btn) {
var tempInput = document.createElement('input');
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
btn.textContent = '✅ Bağlantı Kopyalandı!';
announce('Bağlantı panoya kopyalandı.');
setTimeout(function () {
btn.textContent = '📋 Bağlantıyı Kopyala';
}, 2500);
} catch (e) {
btn.textContent = '❌ Kopyalanamadı';
}
document.body.removeChild(tempInput);
}
copyBtn.focus();
announce('Telefonda devam et penceresi açıldı. QR kodu okutabilir veya bağlantıyı kopyalayabilirsiniz.');
}
// ==========================================================================
// YAKTUBE DOUBLE-TAP TOUCH SEEK ENGINE (v1.5.0)
// ==========================================================================
function showDoubleTapRipple(container, isLeft) {
var className = isLeft ? 'yaktube-doubletap-left' : 'yaktube-doubletap-right';
var existing = container.querySelector('.yaktube-doubletap-overlay.' + className);
if (!existing) {
existing = document.createElement('div');
existing.className = 'yaktube-doubletap-overlay ' + className;
2026-09-19 05:20:33 +03:00
existing.innerHTML =
'<div class="yaktube-ripple-content">' + (isLeft ? '⏪ 10sn Geri' : '10sn İleri ⏩') + '</div>';
container.appendChild(existing);
}
existing.classList.add('yaktube-ripple-active');
clearTimeout(existing._rippleTimer);
existing._rippleTimer = setTimeout(function () {
existing.classList.remove('yaktube-ripple-active');
}, 650);
}
function initTouchSeekGesture(video) {
if (!video) return;
2026-09-19 05:20:33 +03:00
var playerContainer =
video.closest('.video-js, .player-container, .player, my-video-watch') || video.parentElement;
if (!playerContainer || playerContainer.hasAttribute('data-yaktube-touch-seek-hooked')) return;
playerContainer.setAttribute('data-yaktube-touch-seek-hooked', 'true');
var lastTapTime = 0;
var lastTapX = 0;
var tapTimeout = null;
playerContainer.addEventListener(
'touchend',
function (e) {
if (e.changedTouches && e.changedTouches.length === 1) {
var touch = e.changedTouches[0];
var rect = playerContainer.getBoundingClientRect();
var tapX = touch.clientX - rect.left;
var tapY = touch.clientY - rect.top;
var now = Date.now();
if (tapY > rect.height - 50) return;
if (now - lastTapTime < 320 && Math.abs(tapX - lastTapX) < 60) {
e.preventDefault();
clearTimeout(tapTimeout);
lastTapTime = 0;
var isLeft = tapX < rect.width * 0.45;
var isRight = tapX > rect.width * 0.55;
if (isLeft) {
video.currentTime = Math.max(0, video.currentTime - 10);
showDoubleTapRipple(playerContainer, true);
announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime));
} else if (isRight) {
video.currentTime = Math.min(video.duration || 999999, video.currentTime + 10);
showDoubleTapRipple(playerContainer, false);
announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime));
}
} else {
lastTapTime = now;
lastTapX = tapX;
tapTimeout = setTimeout(function () {
lastTapTime = 0;
}, 330);
}
}
},
{ passive: false }
);
}
// ==========================================================================
// YAKTUBE VOICE ASSISTANT COMMANDS HANDLER (v1.5.0)
// ==========================================================================
function handleVoiceAssistantCommand(rawTranscript) {
if (!rawTranscript) return false;
var text = rawTranscript.toLowerCase().trim();
var video = getVideoElement();
if (text === 'durdur' || text === 'duraklat' || text === 'videoyu durdur' || text === 'pause') {
if (video) video.pause();
announce('Video duraklatıldı.', true);
showQuickToast('⏸️ Video Duraklatıldı');
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'oynat' || text === 'başlat' || text === 'devam et' || text === 'videoyu oynat' || text === 'play') {
if (video) video.play();
announce('Video oynatılıyor.', true);
showQuickToast('▶️ Video Oynatılıyor');
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'tam ekran' || text === 'büyük ekran' || text === 'tam ekran yap') {
2026-09-19 05:20:33 +03:00
var playerWrap =
document.querySelector('.player-container, .video-player-container, my-video-watch .player') || video;
if (playerWrap) {
if (!document.fullscreenElement) {
if (playerWrap.requestFullscreen) playerWrap.requestFullscreen();
announce('Tam ekran moduna geçildi.', true);
showQuickToast('⛶ Tam Ekran');
} else {
if (document.exitFullscreen) document.exitFullscreen();
announce('Tam ekrandan çıkıldı.', true);
showQuickToast('⛶ Normal Ekran');
}
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'sesi aç' || text === 'sesi yükselt' || text === 'sesi arttır' || text === 'ses aç') {
if (video) {
video.volume = Math.min(1, video.volume + 0.2);
var pct = Math.round(video.volume * 100);
announce('Ses seviyesi artırıldı: %' + pct, true);
showQuickToast('🔊 Ses: %' + pct);
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'sesi kıs' || text === 'sesi azalt' || text === 'ses kıs') {
if (video) {
video.volume = Math.max(0, video.volume - 0.2);
var pct = Math.round(video.volume * 100);
announce('Ses seviyesi azaltıldı: %' + pct, true);
showQuickToast('🔉 Ses: %' + pct);
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'sessiz' || text === 'sustur' || text === 'sesi kapat' || text === 'mute') {
if (video) {
video.muted = !video.muted;
announce(video.muted ? 'Ses kapatıldı.' : 'Ses açıldı.', true);
showQuickToast(video.muted ? '🔇 Sessiz' : '🔊 Ses Açık');
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'ileri sar' || text === '10 saniye ileri' || text === 'ileri al' || text === 'atla') {
if (video) {
video.currentTime = Math.min(video.duration || 999999, video.currentTime + 10);
announce('10 saniye ileri sarıldı.', true);
showQuickToast('⏩ +10sn İleri');
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'geri sar' || text === '10 saniye geri' || text === 'geri al') {
if (video) {
video.currentTime = Math.max(0, video.currentTime - 10);
announce('10 saniye geri sarıldı.', true);
showQuickToast('⏪ -10sn Geri');
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text.includes('uyku') || text.includes('zamanlayıcı')) {
stopVoiceInsideModal();
closeFullSearchModal();
openSleepTimerModal();
return true;
}
if (text === 'kapat' || text === 'çık' || text === 'iptal') {
stopVoiceInsideModal();
closeFullSearchModal();
announce('Arama penceresi kapatıldı.');
return true;
}
return false;
}
// 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-sleep-btn" class="yaktube-player-action-btn" aria-label="Uyku Zamanlayıcısı (Alt+U)">⏱️ Uyku</button>' +
'<button id="yaktube-bar-qr-btn" class="yaktube-player-action-btn" aria-label="Telefonda Devam Et / QR Kod (Alt+Q)">📱 Telefonda Aç</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);
});
var sleepBtn = bar.querySelector('#yaktube-bar-sleep-btn');
if (sleepBtn) {
sleepBtn.addEventListener('click', function () {
openSleepTimerModal();
});
}
var qrBtn = bar.querySelector('#yaktube-bar-qr-btn');
if (qrBtn) {
qrBtn.addEventListener('click', function () {
openPhoneTransferModal();
});
}
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)
window.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
var sleepModal = document.getElementById('yaktube-sleep-modal');
if (sleepModal) {
sleepModal.remove();
announce('Uyku zamanlayıcısı penceresi kapatıldı.');
return;
}
var qrModal = document.getElementById('yaktube-qr-modal');
if (qrModal) {
qrModal.remove();
announce('Telefonda devam et penceresi kapatıldı.');
return;
}
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;
}
// Admin Login: Alt+A
if (e.altKey && (e.key === 'a' || e.key === 'A') && !isInputFocused()) {
e.preventDefault();
openLocalAdminLoginModal();
return;
}
// Sleep Timer: Alt+U
if (e.altKey && (e.key === 'u' || e.key === 'U') && !isInputFocused()) {
e.preventDefault();
openSleepTimerModal();
return;
}
// Phone QR Transfer: Alt+Q
if (e.altKey && (e.key === 'q' || e.key === 'Q') && !isInputFocused()) {
e.preventDefault();
openPhoneTransferModal();
return;
}
// Help Dialog: Alt+H, F1 or '?' key
if ((e.altKey && (e.key === 'h' || e.key === 'H')) || e.key === 'F1' || e.key === '?') {
if (!isInputFocused()) {
e.preventDefault();
toggleShortcutsModal();
return;
}
}
// 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;
}
if (isInputFocused()) {
return;
}
var video = getVideoElement();
if (!video) return;
var key = e.key.toLowerCase();
var alt = e.altKey;
// Play / Pause (Alt+K, Space or K)
if (key === ' ' || key === 'k' || (alt && key === 'k')) {
e.preventDefault();
if (video.paused) {
video.play();
announce('Video oynatılıyor.');
} else {
video.pause();
announce('Video duraklatıldı.');
}
}
// Seek 10s Back / Forward (Alt+J / Alt+L or J / L)
else if (key === 'j' || (alt && key === 'j')) {
e.preventDefault();
video.currentTime = Math.max(0, video.currentTime - 10);
announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime));
} else if (key === 'l' || (alt && key === 'l')) {
e.preventDefault();
video.currentTime = Math.min(video.duration || 99999, video.currentTime + 10);
announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime));
}
// Seek 5s Back / Forward (ArrowLeft / ArrowRight or Alt+Left/Right)
else if (e.key === 'ArrowLeft' || (alt && e.key === 'ArrowLeft')) {
e.preventDefault();
video.currentTime = Math.max(0, video.currentTime - 5);
announce('5 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime));
} else if (e.key === 'ArrowRight' || (alt && e.key === 'ArrowRight')) {
e.preventDefault();
video.currentTime = Math.min(video.duration || 99999, video.currentTime + 5);
announce('5 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime));
}
// Volume Up / Down (ArrowUp / ArrowDown or Alt+Up/Down)
else if (e.key === 'ArrowUp' || (alt && e.key === 'ArrowUp')) {
e.preventDefault();
video.volume = Math.min(1, (video.volume || 1) + 0.05);
announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100));
} else if (e.key === 'ArrowDown' || (alt && e.key === 'ArrowDown')) {
e.preventDefault();
video.volume = Math.max(0, (video.volume || 1) - 0.05);
announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100));
}
// Mute (Alt+M or M)
else if (key === 'm' || (alt && key === 'm')) {
e.preventDefault();
video.muted = !video.muted;
announce(video.muted ? 'Ses kapatıldı (Sessiz).' : 'Ses açıldı.');
}
// Fullscreen (Alt+F or F)
else if (key === 'f' || (alt && key === 'f')) {
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ı.');
}
}
// Theater Mode (Alt+T or T)
else if (key === 't' || (alt && key === 't')) {
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ı.');
}
// Quick Video Info (Alt+I or I)
else if (key === 'i' || (alt && key === 'i')) {
e.preventDefault();
showQuickVideoInfo();
}
// Subtitles (Alt+C or C)
else if (key === 'c' || (alt && key === 'c')) {
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.');
}
}
// Speed Controls (Alt+. / Alt+, or > / <)
else if ((alt && (e.key === '.' || e.key === '>')) || e.key === '>') {
e.preventDefault();
adjustPlaybackSpeed(true);
} else if ((alt && (e.key === ',' || e.key === '<')) || e.key === '<') {
e.preventDefault();
adjustPlaybackSpeed(false);
}
// Percentage Jumps (0 - 9)
else if (key >= '0' && key <= '9' && !e.ctrlKey && !e.altKey && !e.metaKey) {
e.preventDefault();
var percent = parseInt(key, 10) * 10;
if (video.duration) {
video.currentTime = (video.duration * percent) / 100;
announce('Videonun yüzde ' + percent + ' noktasına atlandı. Konum: ' + formatTime(video.currentTime));
}
}
});
// 5. On-Demand Search & Import with Duplicate Awareness
var lastQuery = null;
var lastSearchTarget = null;
var isSearching = false;
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function formatClockTime(seconds) {
if (isNaN(seconds) || seconds < 0) return '00:00';
var hrs = Math.floor(seconds / 3600);
var mins = Math.floor((seconds % 3600) / 60);
var secs = Math.floor(seconds % 60);
var sSecs = (secs < 10 ? '0' : '') + secs;
if (hrs > 0) {
var sMins = (mins < 10 ? '0' : '') + mins;
return hrs + ':' + sMins + ':' + sSecs;
}
return (mins < 10 ? '0' : '') + mins + ':' + sSecs;
}
function switchSearchTarget(target) {
var radio = document.getElementById(target === 'local' ? 'searchTargetLocal' : 'searchTargetSearchIndex');
if (radio) {
radio.checked = true;
radio.dispatchEvent(new Event('change', { bubbles: true }));
radio.dispatchEvent(new Event('input', { bubbles: true }));
var form = radio.closest('form');
var submitBtn = form ? form.querySelector('.submit-button button') : null;
if (submitBtn) {
submitBtn.click();
return;
}
}
var url = new URL(window.location.href);
url.searchParams.set('searchTarget', target);
window.location.href = url.toString();
}
function ensureFediverseBridge(query, currentSearchTarget) {
var bridge = document.getElementById('yaktube-fediverse-bridge');
if (!bridge) {
bridge = document.createElement('div');
bridge.id = 'yaktube-fediverse-bridge';
bridge.className = 'yaktube-fediverse-bridge';
bridge.setAttribute('role', 'region');
bridge.setAttribute('aria-label', 'Fediverse Video Arama Ağı ve Kapsam Seçimi');
var searchResult = document.querySelector('.search-result, my-search .search-result');
var ondemandContainer = document.getElementById('yaktube-ondemand-container');
if (searchResult && searchResult.parentNode) {
searchResult.parentNode.insertBefore(bridge, searchResult);
} else if (ondemandContainer && ondemandContainer.parentNode) {
ondemandContainer.parentNode.insertBefore(bridge, ondemandContainer.nextSibling);
} else {
var mainCol = document.querySelector('my-search, main, .main-col') || document.body;
mainCol.appendChild(bridge);
}
}
var isFediverse = currentSearchTarget === 'search-index';
bridge.innerHTML =
'<div class="yaktube-fediverse-bridge-content">' +
2026-09-19 05:20:33 +03:00
'<div class="yaktube-fediverse-bridge-left">' +
'<div class="yaktube-fediverse-bridge-badge-wrap">' +
'<span class="yaktube-fediverse-badge">🌐 Fediverse Ağı</span>' +
'<span class="yaktube-fediverse-status">' +
(isFediverse
? 'SepiaSearch Küresel Fediverse Arama Aktif'
: escapeHtml(getInstanceName()) + ' Yerel Arama Aktif') +
'</span>' +
'</div>' +
'<h2 class="yaktube-fediverse-title">🌐 Fediverse & Küresel Ağ Arama Sonuçları</h2>' +
'<p class="yaktube-fediverse-sub">Merkeziyetsiz federe ağdaki (SepiaSearch ve bağımsız PeerTube sunucuları) video sonuçları aşağıda listelenmektedir.</p>' +
'</div>' +
'<div class="yaktube-scope-selector" role="tablist" aria-label="Arama Kapsamı Seçimi">' +
'<button type="button" id="yaktube-scope-global-btn" class="yaktube-scope-btn ' +
(isFediverse ? 'active' : '') +
'" role="tab" aria-selected="' +
(isFediverse ? 'true' : 'false') +
'" aria-label="Tüm Fediverse ve SepiaSearch ağındaki videoları ara" title="Tüm Fediverse genelinde ara">' +
'🌐 Tüm Fediverse (SepiaSearch)' +
'</button>' +
'<button type="button" id="yaktube-scope-local-btn" class="yaktube-scope-btn ' +
(!isFediverse ? 'active' : '') +
'" role="tab" aria-selected="' +
(!isFediverse ? 'true' : 'false') +
'" aria-label="Sadece ' +
escapeHtml(getInstanceName()) +
' yerel sunucusundaki videoları ara" title="Sadece ' +
escapeHtml(getInstanceName()) +
' yerel kütüphanesinde ara">' +
'🏠 Sadece ' +
escapeHtml(getInstanceName()) +
' (Yerel)' +
'</button>' +
'</div>' +
'</div>';
var globalBtn = document.getElementById('yaktube-scope-global-btn');
if (globalBtn) {
globalBtn.addEventListener('click', function () {
if (currentSearchTarget !== 'search-index') {
switchSearchTarget('search-index');
}
});
}
var localBtn = document.getElementById('yaktube-scope-local-btn');
if (localBtn) {
localBtn.addEventListener('click', function () {
if (currentSearchTarget !== 'local') {
switchSearchTarget('local');
}
});
}
}
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, '&quot;') +
'" 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.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 = '📥 ' + escapeHtml(getInstanceName()) + "'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 = '📥 ' + escapeHtml(getInstanceName()) + "'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 = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
alert('İndirme hatası: ' + err.message);
announce('İndirme hatası: ' + err.message, true);
});
}
function checkAndInjectSearchResults() {
// Clean up stray 404 error mascot pages if on search
try {
var errPages = document.querySelectorAll('my-error-page');
2026-09-04 12:54:11 +03:00
errPages.forEach(function (ep) {
ep.remove();
});
} catch (e) {}
2026-09-04 12:54:11 +03:00
// Instant URL fixer: redirect legacy /search/videos to native /search
if (window.location.pathname === '/search/videos' || window.location.pathname.startsWith('/search/videos/')) {
window.location.replace('/search' + window.location.search);
return;
}
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();
var existingBridge = document.getElementById('yaktube-fediverse-bridge');
if (existingBridge) existingBridge.remove();
lastQuery = null;
lastSearchTarget = null;
return;
}
var query = searchParam.trim();
// Ensure Fediverse Global Search (SepiaSearch) is default target if not specified in URL
if (!url.searchParams.has('searchTarget')) {
url.searchParams.set('searchTarget', 'search-index');
window.location.replace(url.toString());
return;
}
var currentSearchTarget = url.searchParams.get('searchTarget') || 'search-index';
var queryKey = query + '___' + currentSearchTarget;
var lastQueryKey = lastQuery + '___' + lastSearchTarget;
if (queryKey === lastQueryKey) {
var existingBridge = document.getElementById('yaktube-fediverse-bridge');
if (!existingBridge && document.querySelector('my-search')) {
ensureFediverseBridge(query, currentSearchTarget);
}
return;
}
lastQuery = query;
lastSearchTarget = currentSearchTarget;
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ı YouTube ve ' + getInstanceName() + ' Arama Sonuçları');
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">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
escapeHtml(query) +
'" için Sonuçlar Taranıyor...</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-loading">' +
'<div class="yaktube-spinner" aria-hidden="true"></div>' +
2026-09-19 05:20:33 +03:00
'<div style="margin-top: 10px;">YouTube ve ' +
escapeHtml(getInstanceName()) +
' yerel kütüphanesindeki eşleşmeler taranıyor...</div>' +
'</div>';
var shortcutsBtn = document.getElementById('yaktube-open-shortcuts');
if (shortcutsBtn) {
shortcutsBtn.addEventListener('click', toggleShortcutsModal);
}
ensureFediverseBridge(query, currentSearchTarget);
announce(query + ' için arama başlatıldı.');
isSearching = true;
if (window.__yaktube_search_abort_controller__) {
try {
window.__yaktube_search_abort_controller__.abort();
} catch (e) {}
}
var abortCtrl = typeof AbortController !== 'undefined' ? new AbortController() : null;
window.__yaktube_search_abort_controller__ = abortCtrl;
var fetchOpts = abortCtrl ? { signal: abortCtrl.signal } : {};
Promise.all([
fetch('/api-custom/youtube-search?q=' + encodeURIComponent(query), fetchOpts)
.then(function (res) {
return res.json();
})
.catch(function (err) {
if (err && err.name === 'AbortError') return { results: [] };
console.warn('[YakTube] YouTube search fetch error:', err);
return { results: [] };
}),
fetch('/api/v1/search/videos?search=' + encodeURIComponent(query) + '&isLocal=true&count=12', fetchOpts)
.then(function (res) {
return res.json();
})
.catch(function (err) {
if (err && err.name === 'AbortError') return { total: 0, data: [] };
console.warn('[YakTube] Local videos search fetch error:', err);
return { total: 0, data: [] };
})
])
.then(function (responses) {
var ytData = responses[0] || {};
var localData = responses[1] || {};
var rawYtResults = ytData.results || [];
var uniqueYtResults = [];
var seenIds = {};
rawYtResults.forEach(function (item) {
if (item && item.id && !seenIds[item.id] && item.title) {
seenIds[item.id] = true;
uniqueYtResults.push(item);
}
});
var ytCount = uniqueYtResults.length;
var localResults = localData.data || [];
var localTotal = typeof localData.total === 'number' ? localData.total : localResults.length;
if (ytCount === 0 && localResults.length === 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;">"' +
escapeHtml(query) +
'" için Sonuç Bulunamadı</h2>' +
'</div>' +
'<button class="yaktube-shortcuts-btn" id="yaktube-open-shortcuts" aria-label="Klavye Kısayolları Kılavuzunu Aç">' +
'⌨️ Kısayollar (?)' +
'</button>' +
'</div>';
announce(query + ' için YouTube veya yerel sunucuda eşleşen video bulunamadı.');
} else {
var visibleYtCount = Math.min(12, ytCount);
function renderAll() {
// 1. YouTube Section
var ytHeaderHtml = '';
var ytCardsHtml = '';
var ytLoadMoreHtml = '';
if (ytCount > 0) {
var countText = ytCount === 1 ? '1 Video Bulundu' : ytCount + ' Video Bulundu';
ytHeaderHtml =
'<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;">"' +
escapeHtml(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>';
var visibleResults = uniqueYtResults.slice(0, visibleYtCount);
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;">✅ ' +
escapeHtml(getInstanceName()) +
2026-09-19 05:20:33 +03:00
"'da Mevcut</span>";
}
var actionButton = '';
if (isLive) {
actionButton =
'<button class="yaktube-live-btn" data-url="' +
escapeHtml(item.url) +
'" data-title="' +
escapeHtml(item.title) +
'" 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="' +
escapeHtml(item.title) +
' canlı yayınını hemen izle">🔴 Canlı Yayını İzle</button>';
} else if (isLocal && item.localVideo && item.localVideo.url) {
actionButton =
'<a href="' +
escapeHtml(item.localVideo.url) +
'" class="yaktube-play-btn" aria-label="' +
escapeHtml(item.title) +
2026-09-19 05:20:33 +03:00
' videosunu ' +
escapeHtml(getInstanceName()) +
2026-09-19 05:20:33 +03:00
'\'dan hemen izle">▶️ ' +
escapeHtml(getInstanceName()) +
"'dan İzle</a>";
} else {
actionButton =
'<button class="yaktube-import-btn" data-url="' +
escapeHtml(item.url) +
'" data-title="' +
escapeHtml(item.title) +
'" aria-label="' +
escapeHtml(item.title) +
' videosunu ' +
escapeHtml(getInstanceName()) +
'\'a aktar ve izle">📥 ' +
escapeHtml(getInstanceName()) +
2026-09-19 05:20:33 +03:00
"'a Aktar & İzle</button>";
}
2026-09-19 05:20:33 +03:00
var durationText = isLive ? 'CANLI' : item.durationFormatted || formatClockTime(item.duration);
var cardLabel =
item.title +
(isLive ? ' (Canlı Yayın)' : isLocal ? ' (' + getInstanceName() + ' kütüphanenizde mevcut)' : '') +
', ' +
item.channel +
' kanalı, Süre: ' +
durationText;
ytCardsHtml +=
'<div class="yaktube-card" role="article" tabindex="0" aria-label="' +
escapeHtml(cardLabel) +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
escapeHtml(item.thumbnail) +
'" alt="' +
escapeHtml(item.title) +
'" loading="lazy" />' +
'<span class="yaktube-duration-badge" style="' +
(isLive ? 'background:#dc2626;font-weight:700;' : '') +
'" aria-label="Süre">' +
escapeHtml(durationText) +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
statusBadge +
'<h3 class="yaktube-video-name" title="' +
escapeHtml(item.title) +
'" style="margin:0; font-size:13px; font-weight:600; line-height:1.35;">' +
escapeHtml(item.title) +
'</h3>' +
'<div class="yaktube-channel-name">' +
escapeHtml(item.channel) +
'</div>' +
'</div>' +
actionButton +
'</div>' +
'</div>';
});
if (visibleYtCount < ytCount) {
ytLoadMoreHtml =
'<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 (' +
visibleYtCount +
' / ' +
ytCount +
')' +
'</button>' +
'</div>';
}
}
// 2. Local Videos Shelf
var localShelfHtml = '';
if (localResults.length > 0) {
var localCardsHtml = '';
localResults.forEach(function (lItem) {
2026-09-19 05:20:33 +03:00
var lThumb =
lItem.thumbnailPath ||
(lItem.thumbnails && lItem.thumbnails[0]
? lItem.thumbnails[0].fileUrl
: '/client/assets/images/default-thumbnail.jpg');
var lDuration = formatClockTime(lItem.duration);
2026-09-19 05:20:33 +03:00
var lChannel =
(lItem.channel && (lItem.channel.displayName || lItem.channel.name)) ||
(lItem.account && (lItem.account.displayName || lItem.account.name)) ||
getInstanceName();
var lWatchUrl = lItem.url || '/videos/watch/' + (lItem.shortUUID || lItem.uuid);
var lViews = (lItem.views || 0) + ' görüntüleme';
2026-09-19 05:20:33 +03:00
var lLabel =
lItem.name +
', ' +
lChannel +
', ' +
getInstanceName() +
' yerel sunucusunda mevcut, Süre: ' +
lDuration;
localCardsHtml +=
'<div class="yaktube-card yaktube-local-card" role="article" tabindex="0" aria-label="' +
escapeHtml(lLabel) +
'">' +
2026-09-19 05:20:33 +03:00
'<a href="' +
escapeHtml(lWatchUrl) +
'" style="text-decoration:none; color:inherit; display:flex; flex-direction:column; height:100%;">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
escapeHtml(lThumb) +
'" alt="' +
escapeHtml(lItem.name) +
'" loading="lazy" onerror="this.src=\'/client/assets/images/default-thumbnail.jpg\'" />' +
'<span class="yaktube-duration-badge" aria-label="Süre">' +
escapeHtml(lDuration) +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
2026-09-19 05:20:33 +03:00
'<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); margin-bottom: 6px; display: inline-block;">✅ ' +
escapeHtml(getInstanceName()) +
"'da Mevcut</span>" +
'<h3 class="yaktube-video-name" title="' +
escapeHtml(lItem.name) +
'" style="margin:0; font-size:13px; font-weight:600; line-height:1.35;">' +
escapeHtml(lItem.name) +
'</h3>' +
'<div class="yaktube-channel-name">' +
2026-09-19 05:20:33 +03:00
escapeHtml(lChannel) +
' • ' +
escapeHtml(lViews) +
'</div>' +
'</div>' +
2026-09-19 05:20:33 +03:00
'<div class="yaktube-play-btn" style="margin-top:8px;">▶️ ' +
escapeHtml(getInstanceName()) +
"'dan Hemen İzle</div>" +
'</div>' +
'</a>' +
'</div>';
});
localShelfHtml =
2026-09-19 05:20:33 +03:00
'<div id="yaktube-local-shelf" class="yaktube-local-shelf" role="region" aria-label="' +
escapeHtml(getInstanceName()) +
' Yerel Kütüphanesi">' +
'<div class="yaktube-header" style="margin-top: 24px; border-bottom-color: rgba(16, 185, 129, 0.3);">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">✅ Yerel Kütüphane</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
escapeHtml(query) +
2026-09-19 05:20:33 +03:00
'" için ' +
escapeHtml(getInstanceName()) +
' Sunucusunda ' +
localTotal +
' Video Bulundu</h2>' +
'</div>' +
'<span class="yaktube-local-shelf-sub">Doğrudan sunucumuzda barındırılan, yüksek hızlı ve anında izlenebilen videolar</span>' +
'</div>' +
'<div class="yaktube-grid">' +
localCardsHtml +
'</div>' +
'</div>';
}
container.innerHTML =
ytHeaderHtml +
(ytCount > 0 ? '<div class="yaktube-grid">' + ytCardsHtml + '</div>' : '') +
ytLoadMoreHtml +
localShelfHtml;
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 () {
visibleYtCount = Math.min(visibleYtCount + 6, ytCount);
renderAll();
announce(visibleYtCount + ' 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);
});
});
}
renderAll();
var announceMsg = '"' + query + '" için arama sonuçları listelendi: ';
if (localTotal > 0) {
announceMsg += localTotal + ' adet yerel ' + getInstanceName() + ' videosu, ';
}
if (ytCount > 0) {
announceMsg += ytCount + ' adet YouTube sonucu ve ';
}
announceMsg += 'Fediverse sonuçları hazır.';
announce(announceMsg);
}
})
.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">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">Arama sırasında bir bağlantı sorunu oluştu.</h2>' +
'</div>' +
'</div>';
announce('Arama sırasında bir bağlantı sorunu oluştu.');
})
.finally(function () {
isSearching = false;
ensureFediverseBridge(query, currentSearchTarget);
});
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// 8. FULL YOUTUBE SEARCH & VOICE MODAL ENGINE (v1.3.3 - Guaranteed Close Fix)
// --------------------------------------------------------------------------
var activeVoiceRec = null;
var isVoiceListening = false;
// Dynamic Search History & Trending Helpers with YakNet Cloud Sync
function isUserLoggedIn() {
try {
var token = localStorage.getItem('access_token');
var username = localStorage.getItem('username');
return Boolean(token || username);
} catch (e) {
return false;
}
}
function isAccountSyncEnabled() {
try {
var userPref = localStorage.getItem('yaktube_sync_search_history');
if (userPref === 'false') return false;
return isUserLoggedIn();
} catch (e) {
return false;
}
}
function getRecentSearches() {
try {
var data = localStorage.getItem('yaktube_recent_searches');
if (data) {
var parsed = JSON.parse(data);
if (Array.isArray(parsed) && parsed.length > 0) return parsed;
}
} catch (e) {}
// Default initial recent searches for first-time visitors
var defaults = ['Tarkan', 'Barış Manço', 'Yapay Zeka', 'Canlı Haber'];
if (!isUserLoggedIn()) {
try {
localStorage.setItem('yaktube_recent_searches', JSON.stringify(defaults));
} catch (e) {}
}
return defaults;
}
function callCloudSearchHistory(method, queryParams, body, onSuccess, onError) {
var token = localStorage.getItem('access_token');
var email = localStorage.getItem('email');
var username = localStorage.getItem('username');
var headers = {};
if (body) headers['Content-Type'] = 'application/json';
if (token) headers['Authorization'] = 'Bearer ' + token;
if (email) headers['X-User-Email'] = email;
if (username) headers['X-User-Username'] = username;
var authUrl = 'https://auth.yakhub.com.tr/api/yaktube/search-history' + (queryParams || '');
var bridgeUrl = '/api-custom/search-history' + (queryParams || '');
var options = {
method: method,
headers: headers,
credentials: 'include'
};
if (body) options.body = JSON.stringify(body);
fetch(authUrl, options)
.then(function (res) {
if (!res.ok) throw new Error('Auth server status ' + res.status);
return res.json();
})
.then(function (data) {
if (typeof onSuccess === 'function') onSuccess(data);
})
.catch(function () {
// Fallback to local PeerTube bridge proxy
fetch(bridgeUrl, options)
2026-09-04 12:54:11 +03:00
.then(function (res) {
return res.json();
})
.then(function (data) {
if (typeof onSuccess === 'function') onSuccess(data);
})
.catch(function (e) {
if (typeof onError === 'function') onError(e);
});
});
}
var isSyncingHistory = false;
function syncCloudSearchHistory(callback) {
if (!isAccountSyncEnabled() || isSyncingHistory) return;
isSyncingHistory = true;
2026-09-04 12:54:11 +03:00
callCloudSearchHistory(
'GET',
'',
null,
function (data) {
isSyncingHistory = false;
if (data && data.status === 'success' && Array.isArray(data.history)) {
if (data.history.length > 0) {
localStorage.setItem('yaktube_recent_searches', JSON.stringify(data.history));
}
if (typeof callback === 'function') callback();
}
2026-09-04 12:54:11 +03:00
},
function () {
isSyncingHistory = false;
}
2026-09-04 12:54:11 +03:00
);
}
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 > 12) list = list.slice(0, 12);
localStorage.setItem('yaktube_recent_searches', JSON.stringify(list));
} catch (e) {}
// If user is logged in and sync is active, sync with YakNet account
if (isAccountSyncEnabled()) {
callCloudSearchHistory('POST', '', { query: q, device_name: 'YakTube Web' });
}
}
function clearRecentSearches() {
try {
localStorage.removeItem('yaktube_recent_searches');
} catch (e) {}
if (isAccountSyncEnabled()) {
callCloudSearchHistory('DELETE', '', null);
}
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' }
];
// Auto capture search query from URL into recent searches
function captureUrlSearchQuery() {
try {
var params = new URLSearchParams(window.location.search);
var q = params.get('search') || params.get('q');
if (q && q.trim().length >= 2) {
saveRecentSearch(q.trim());
}
} catch (e) {}
}
captureUrlSearchQuery();
window.addEventListener('popstate', captureUrlSearchQuery);
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 loggedIn = isUserLoggedIn();
var syncEnabled = isAccountSyncEnabled();
var html = '';
// 1. Recent searches section
html +=
'<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; flex-wrap: wrap; gap: 8px;">' +
'<div class="yaktube-modal-section-title" style="margin-bottom: 0;">🕒 Son Aramalarınız</div>' +
'<div style="display: flex; align-items: center; gap: 8px;">';
if (loggedIn) {
var syncTitle = syncEnabled
? 'YakNet Bulut Eşitlemesi Aktif (Tüm cihazlarınızda senkronize edilir. Kapatmak için tıklayın.)'
: 'Eşitleme Kapalı (Aramalar sadece bu cihazda saklanır. Açmak için tıklayın.)';
var syncText = syncEnabled ? '☁️ Eşitleme: Açık' : '☁️ Eşitleme: Kapalı';
var syncBg = syncEnabled ? '#0f766e' : '#334155';
var syncColor = syncEnabled ? '#5eead4' : '#94a3b8';
html +=
'<button id="yaktube-sync-toggle-btn" style="background: ' +
syncBg +
'; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 2px 8px; color: ' +
syncColor +
'; font-size: 11px; font-weight: 600; cursor: pointer; transition: all 0.2s ease;" title="' +
syncTitle +
'" aria-label="' +
syncTitle +
'">' +
syncText +
'</button>';
} else {
html +=
'<span style="font-size: 11px; color: #94a3b8; background: #1e293b; border-radius: 6px; padding: 2px 8px;" title="Misafir modundasınız, aramalar sadece bu cihazda saklanır">🔒 Bu Cihazda</span>';
}
if (recent.length > 0) {
html +=
'<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>';
}
html += '</div></div>';
if (recent.length > 0) {
html += '<div class="yaktube-modal-chips-grid" style="margin-bottom: 18px;">';
recent.forEach(function (term) {
var safeTerm = term.replace(/"/g, '&quot;');
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, '&quot;');
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();
});
}
var syncToggleBtn = container.querySelector('#yaktube-sync-toggle-btn');
if (syncToggleBtn) {
syncToggleBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var current = localStorage.getItem('yaktube_sync_search_history') !== 'false';
var next = !current;
localStorage.setItem('yaktube_sync_search_history', next ? 'true' : 'false');
if (next) {
announce('YakNet arama geçmişi eşitlemesi açıldı.');
syncCloudSearchHistory(renderDynamicSearchChips);
} else {
announce('Arama geçmişi eşitlemesi kapatıldı. Aramalar sadece bu cihazda saklanacak.');
}
renderDynamicSearchChips();
});
}
}
function createFullSearchModal() {
var existingModal = document.getElementById('yaktube-full-search-modal');
if (existingModal) return existingModal;
var modal = document.createElement('div');
modal.id = 'yaktube-full-search-modal';
modal.className = 'yaktube-full-search-modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-labelledby', 'yaktube-modal-search-title');
modal.innerHTML =
'<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">' +
'<input id="yaktube-modal-input" type="search" placeholder="Video veya kanal ara..." aria-label="Arama Metni">' +
'<button id="yaktube-modal-clear-btn" class="yaktube-modal-clear-btn" aria-label="Metni Temizle">✕</button>' +
'</div>' +
'<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 id="yaktube-modal-dynamic-chips-section"></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>' +
'</div>' +
'</div>' +
'</div>';
document.body.appendChild(modal);
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');
// 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';
}
});
clearBtn.addEventListener('click', function () {
inp.value = '';
clearBtn.style.display = 'none';
inp.focus();
announce('Arama metni temizlendi.');
});
// Close on Back button or Escape
backBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
closeFullSearchModal();
});
modal.addEventListener('click', function (e) {
if (e.target === modal) {
closeFullSearchModal();
}
});
// Search execute
function executeSearch(query) {
var q = (query || inp.value || '').trim();
if (q) {
saveRecentSearch(q);
announce('"' + q + '" için arama yapılıyor...', true);
closeFullSearchModal();
window.location.href = '/search?search=' + encodeURIComponent(q) + '&searchTarget=search-index';
}
}
window.__yaktube_execute_search__ = executeSearch;
renderDynamicSearchChips();
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();
});
return modal;
}
function openFullSearchModal(initialQuery, autoStartVoice) {
var modal = createFullSearchModal();
modal.classList.add('yaktube-modal-open');
modal.style.display = 'flex';
renderDynamicSearchChips();
syncCloudSearchHistory(renderDynamicSearchChips);
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';
setTimeout(function () {
inp.focus();
inp.select();
}, 50);
}
announce('Arama penceresi açıldı. Arayacağınız konuyu yazın veya sesle arayın.', true);
if (autoStartVoice) {
startVoiceInsideModal();
}
}
function closeFullSearchModal() {
var modal = document.getElementById('yaktube-full-search-modal');
if (modal) {
modal.classList.remove('yaktube-modal-open');
modal.style.display = 'none';
}
stopVoiceInsideModal();
announce('Arama penceresi kapatıldı.');
var trigger = document.querySelector('.yaktube-search-trigger-pill');
if (trigger) {
try {
trigger.focus();
} catch (e) {}
}
}
function toggleVoiceInsideModal() {
if (isVoiceListening) {
stopVoiceInsideModal();
} else {
startVoiceInsideModal();
}
}
function startVoiceInsideModal() {
var SpeechRec = window.SpeechRecognition || window.webkitSpeechRecognition;
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...';
if (!SpeechRec) {
if (statusText) statusText.textContent = 'Tarayıcıda Ses Tanıma Desteklenmiyor';
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.');
return;
}
announce('Sesli arama dinleniyor, lütfen aramak istediğiniz videoyu söyleyin.', true);
try {
activeVoiceRec = new SpeechRec();
activeVoiceRec.lang = navigator.language || 'tr-TR';
activeVoiceRec.continuous = false;
activeVoiceRec.interimResults = true;
activeVoiceRec.maxAlternatives = 1;
isVoiceListening = true;
activeVoiceRec.onresult = function (ev) {
if (ev.results && ev.results[0]) {
var transcript = ev.results[0][0].transcript.trim();
if (transcriptEl) transcriptEl.textContent = '"' + transcript + '"';
if (inp) inp.value = transcript;
if (ev.results[0].isFinal) {
if (handleVoiceAssistantCommand(transcript)) {
return;
}
announce('"' + transcript + '" için arama başlatılıyor...', true);
setTimeout(function () {
closeFullSearchModal();
2026-09-19 05:20:33 +03:00
window.location.href =
'/search?search=' + encodeURIComponent(transcript) + '&searchTarget=search-index';
}, 500);
}
}
};
activeVoiceRec.onerror = function (ev) {
if (statusText) statusText.textContent = 'Ses Algılanamadı';
if (transcriptEl) transcriptEl.textContent = 'Tekrar denemek için mikrofona dokunun.';
stopVoiceInsideModal();
};
activeVoiceRec.onend = function () {
stopVoiceInsideModal();
};
activeVoiceRec.start();
} catch (err) {
stopVoiceInsideModal();
}
}
function stopVoiceInsideModal() {
isVoiceListening = false;
if (activeVoiceRec) {
try {
activeVoiceRec.stop();
} catch (e) {}
activeVoiceRec = null;
}
var modal = document.getElementById('yaktube-full-search-modal');
if (modal) {
var voiceSec = modal.querySelector('#yaktube-modal-voice-section');
if (voiceSec) voiceSec.style.display = 'none';
}
}
function transformToYouTubeSearchBar(searchInp, searchContainer) {
if (searchContainer.hasAttribute('data-yaktube-trigger-styled')) return;
searchContainer.setAttribute('data-yaktube-trigger-styled', 'true');
// Hide original native inputs
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');
}
});
// 1. Create Outer Wrapper
var outerWrap = searchContainer.querySelector('.yaktube-search-outer-wrap');
if (!outerWrap) {
outerWrap = document.createElement('div');
outerWrap.className = 'yaktube-search-outer-wrap';
searchContainer.appendChild(outerWrap);
}
// 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';
triggerPill.setAttribute('aria-label', 'Ara');
triggerPill.setAttribute('title', 'Arama Yap (/)');
triggerPill.innerHTML =
'<span class="yaktube-search-trigger-icon">🔍</span>' +
'<span class="yaktube-search-trigger-text">Ara...</span>' +
'<span class="yaktube-search-trigger-shortcut">/</span>';
outerWrap.appendChild(triggerPill);
triggerPill.addEventListener('click', function (e) {
e.preventDefault();
openFullSearchModal('', false);
});
}
// 3. YouTube Circular Voice Button (🎙️) Beside the Pill Box
var voiceBtn = outerWrap.querySelector('.yaktube-voice-search-btn');
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 = '🎙️';
outerWrap.appendChild(voiceBtn);
voiceBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
openFullSearchModal('', true);
});
}
}
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');
if (searchInp) {
transformToYouTubeSearchBar(searchInp, container);
}
});
// 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);
}
});
}
// Global Keyboard Shortcuts
document.addEventListener('keydown', function (e) {
var activeTag = document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
var isInputActive = activeTag === 'input' || activeTag === 'textarea';
// Shift + V: Open YouTube Voice Search Modal
if (e.shiftKey && (e.key === 'V' || e.key === 'v') && !isInputActive) {
e.preventDefault();
openFullSearchModal('', true);
}
// Escape: Close Full Search Modal
else if (e.key === 'Escape' || e.keyCode === 27) {
var modal = document.getElementById('yaktube-full-search-modal');
if (modal && modal.classList.contains('yaktube-modal-open')) {
e.preventDefault();
closeFullSearchModal();
}
}
// / or S: Open Search Modal
else if ((e.key === '/' || e.key === 's' || e.key === 'S') && !isInputActive && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
openFullSearchModal('', false);
}
});
// Automatically trigger cloud search history pull on startup if logged in
try {
if (isAccountSyncEnabled()) {
syncCloudSearchHistory();
}
} catch (e) {}
// 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, '&quot;') +
' videosunu ' +
escapeHtml(getInstanceName()) +
'\'dan hemen izle">▶️ Hemen İzle</a>'
: '<button class="yaktube-import-btn yaktube-related-import" data-url="' +
item.url +
'" data-title="' +
item.title.replace(/"/g, '&quot;') +
'" style="font-size:11px; padding:6px 10px;" aria-label="' +
item.title.replace(/"/g, '&quot;') +
' 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, '&quot;') +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
item.thumbnail +
'" alt="' +
item.title.replace(/"/g, '&quot;') +
'" 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.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 && navigator.userAgent.indexOf('YakTubeMobileApp') !== -1) ||
(typeof window.matchMedia === 'function' &&
window.matchMedia('(display-mode: standalone)') &&
window.matchMedia('(display-mode: standalone)').matches) ||
(window.navigator && 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', getInstanceName() + ' 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(getInstanceName() + ' uygulaması cihazınıza yükleniyor.');
}
deferredInstallPrompt = null;
});
}
});
headerButtons.prepend(installBtn);
}
function isOfficialYakTubeServer() {
var host = (window.location.hostname || '').toLowerCase();
// Sadece ve sadece resmi YakTube alan adında veya yerel test ortamında çalışsın
// Diğer PeerTube sunucularında yaktube.apk dosyası olmayacağı için ASLA gösterilmez
return host === 'yaktube.yakhub.com.tr' || host === '127.0.0.1' || host === 'localhost';
}
function injectApkDownloadButton() {
if (!isOfficialYakTubeServer()) {
var existBtnOther = document.getElementById('yaktube-apk-download-btn');
if (existBtnOther) existBtnOther.remove();
return;
}
// 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() {
if (!isOfficialYakTubeServer()) {
var existLinkOther = document.getElementById('yaktube-sidebar-apk-link');
if (existLinkOther) existLinkOther.remove();
return;
}
// 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);
}
// 9. Background Play (Screen-Off / Minimize) & MediaSession Lock-Screen Controls
var videoState = {
hasPlayed: false,
explicitlyPausedByClick: false,
lastPlayTime: 0
};
function attachVideoListeners(video) {
if (!video || video.hasAttribute('data-yaktube-bgplay-hooked')) return;
video.setAttribute('data-yaktube-bgplay-hooked', 'true');
try {
initTouchSeekGesture(video);
} catch (e) {}
video.addEventListener('play', function () {
videoState.hasPlayed = true;
videoState.explicitlyPausedByClick = false;
videoState.lastPlayTime = Date.now();
updateMediaSessionMetadata(video);
});
video.addEventListener('ended', function () {
videoState.hasPlayed = false;
videoState.explicitlyPausedByClick = true;
});
video.addEventListener('timeupdate', function () {
updateMediaSessionPosition(video);
});
}
// Hook manual play/pause buttons
document.addEventListener(
'click',
function (e) {
var target = e.target;
if (!target) return;
var btn = target.closest(
'.vjs-play-control, button[title*="Duraklat"], button[aria-label*="Duraklat"], #yaktube-bar-play-btn'
);
if (btn) {
var video = getVideoElement();
if (video && !video.paused) {
videoState.explicitlyPausedByClick = true;
}
}
},
true
);
function handleVisibilityChange() {
var video = getVideoElement();
if (!video) return;
// When screen turns off / tab is hidden:
if (document.hidden || document.webkitHidden) {
if (videoState.hasPlayed && !videoState.explicitlyPausedByClick) {
// Unconditionally keep audio stream active
setTimeout(function () {
if (video.paused && !videoState.explicitlyPausedByClick) {
video.play().catch(function () {});
}
}, 100);
setTimeout(function () {
if (video.paused && !videoState.explicitlyPausedByClick) {
video.play().catch(function () {});
}
}, 300);
}
}
}
document.addEventListener('visibilitychange', handleVisibilityChange, false);
document.addEventListener('webkitvisibilitychange', handleVisibilityChange, false);
window.addEventListener('pagehide', 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) {}
}
// 10. Unified Debounced MutationObserver & Reactive SPA Engine (v1.5.0)
var unifiedTimer = null;
function runUnifiedCycle(isNav) {
try {
autoFixDOM();
} catch (e) {}
try {
var video = getVideoElement();
if (video) attachVideoListeners(video);
} catch (e) {}
try {
checkAndInjectSearchResults();
} catch (e) {}
try {
injectSearchEnhancements();
} catch (e) {}
try {
checkAndInjectRelatedVideos(Boolean(isNav));
} catch (e) {}
try {
restoreActiveLiveStream();
} catch (e) {}
try {
checkAndInjectWatchPageComments();
} catch (e) {}
try {
injectPWAInstallButton();
} catch (e) {}
try {
injectApkDownloadButton();
} catch (e) {}
try {
injectSidebarApkLink();
} catch (e) {}
}
function scheduleUnifiedCycle(immediate, debounceDelay) {
if (unifiedTimer) {
clearTimeout(unifiedTimer);
unifiedTimer = null;
}
if (immediate) {
runUnifiedCycle(true);
return;
}
var delay = typeof debounceDelay === 'number' ? debounceDelay : 150;
unifiedTimer = setTimeout(function () {
unifiedTimer = null;
runUnifiedCycle(false);
}, delay);
}
// SPA Navigation Handler
function onLocationChange() {
currentActiveVideoKey = null;
scheduleUnifiedCycle(true);
setTimeout(function () {
scheduleUnifiedCycle(false, 0);
}, 350);
}
window.addEventListener('popstate', onLocationChange);
// History pushState / replaceState interception for seamless Angular routing
(function hookHistory() {
try {
var origPush = history.pushState;
var origReplace = history.replaceState;
if (origPush) {
history.pushState = function () {
var ret = origPush.apply(this, arguments);
try {
onLocationChange();
} catch (e) {}
return ret;
};
}
if (origReplace) {
history.replaceState = function () {
var ret = origReplace.apply(this, arguments);
try {
onLocationChange();
} catch (e) {}
return ret;
};
}
} catch (e) {}
})();
// MutationObserver: react immediately to DOM changes instead of CPU polling
if (typeof MutationObserver !== 'undefined') {
try {
var observer = new MutationObserver(function () {
scheduleUnifiedCycle(false, 150);
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', function () {
observer.observe(document.body, { childList: true, subtree: true });
});
}
} catch (e) {}
}
// Gentle 3.5s heartbeat fallback (replaces aggressive 800ms polling, reduces idle CPU by 80%)
setInterval(function () {
scheduleUnifiedCycle(false, 0);
}, 3500);
// Initial trigger
runUnifiedCycle(true);
})();
} catch (err) {
console.error('[YakTube] Safe catch error:', err);
}