From 774c8f0f0248f4c3097605804982142cd5e54922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Yak=C4=B1=C5=9Ft=C4=B1r?= Date: Sun, 30 Aug 2026 11:38:46 +0300 Subject: [PATCH] feat(a11y): add Alt modifier shortcuts, Turkish Q key mapping and accessible player action bar v1.4.1 --- client.js | 245 +++++++++++++++++++++++++++++++++++++-------------- package.json | 2 +- style.css | 51 +++++++++++ 3 files changed, 230 insertions(+), 68 deletions(-) diff --git a/client.js b/client.js index 657e1fb..de15fde 100644 --- a/client.js +++ b/client.js @@ -339,6 +339,7 @@ // 3. Intelligent Branding & A11y DOM Auto-Fixer function autoFixDOM() { + injectAccessiblePlayerBar(); injectYakNetSSO(); // Aggressively remove generic Framasoft Open-in-App / Play Store banners @@ -576,7 +577,7 @@ setInterval(autoFixDOM, 1000); - // 3. Keyboard Shortcuts Modal & Quick Info Engine (v1.4.0) + // 3. Screen-Reader & Turkish Q Optimized Shortcuts & Player Bar (v1.4.1) function toggleShortcutsModal() { var modal = document.getElementById('yaktube-shortcuts-dialog'); if (modal) { @@ -597,27 +598,27 @@ modal.setAttribute('aria-modal', 'true'); modal.setAttribute('aria-labelledby', 'yaktube-shortcuts-title'); modal.innerHTML = - '
' + + '
' + '
' + - '

⌨️ Video Oynatıcı & Gezinme Kısayolları

' + + '

⌨️ Klavye Kısayolları (Ekran Okuyucu Uyumlu)

' + '' + '
' + + '
💡 Ekran Okuyucu Notu: Sanal imleç (Browse mode) açıkken kısayolları Alt tuşuyla birlikte kullanabilir veya video oynatıcı butonlarından faydalanabilirsiniz.
' + '' + - '' + + '' + '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' + '
Kısayolİşlev
Kısayol (Evrensel / Tek Tuş)İşlev
Space veya KOynat / Duraklat
J / L10 saniye geri / ileri sar
Sol / Sağ Ok5 saniye geri / ileri sar
Yukarı / Aşağı OkSesi %5 artır / azalt
MSesi Kapat / Aç (Mute)
FTam Ekran
TSinema / Tiyatro Modu
CAltyazıları Aç / Kapat
IHızlı Video Bilgisi & İstatistikler
> / < (Shift+.)Oynatma hızını artır / azalt
0 - 9Videonun %0 - %90 noktasına atla
/Arama penceresini aç
Shift + VSesli aramayı başlat
Alt + K veya SpaceOynat / Duraklat
Alt + J veya J10 saniye geri sar
Alt + L veya L10 saniye ileri sar
Alt + Yukarı / AşağıSesi %5 artır / azalt
Alt + M veya MSesi Kapat / Aç (Mute)
Alt + T veya TSinema / Tiyatro Modu
Alt + I veya IHızlı Video Bilgisi & İstatistikler
Alt + C veya CAltyazıları Aç / Kapat
Alt + . / Alt + ,Oynatma hızını artır / azalt
Alt + S veya / (Shift+7)Arama penceresini aç
Alt + V veya Shift + VSesli aramayı başlat
Alt + H veya F1 veya ?Bu kılavuzu aç / kapat
' + '
'; @@ -668,8 +669,8 @@ var curTime = formatTime(video.currentTime); var totalTime = formatTime(video.duration || 0); var remaining = formatTime((video.duration || 0) - video.currentTime); - var speed = video.playbackRate + 'x'; - var volume = Math.round(video.volume * 100) + '%'; + var speed = (video.playbackRate || 1) + 'x'; + var volume = Math.round((video.volume || 1) * 100) + '%'; var quality = video.videoHeight ? video.videoHeight + 'p' : 'Otomatik'; // Visual Toast @@ -726,7 +727,111 @@ var speedSteps = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; - // 4. Global Keydown Listener (Pro Video Player Engine) + function adjustPlaybackSpeed(increase) { + var video = getVideoElement(); + if (!video) return; + var curSpeed = video.playbackRate || 1; + if (increase) { + var nextIdx = speedSteps.findIndex(function (s) { + return s > curSpeed; + }); + video.playbackRate = nextIdx !== -1 ? speedSteps[nextIdx] : 2; + announce('Oynatma hızı ' + video.playbackRate + ' katına çıkarıldı.'); + } else { + var prevSteps = speedSteps.filter(function (s) { + return s < curSpeed; + }); + video.playbackRate = prevSteps.length > 0 ? prevSteps[prevSteps.length - 1] : 0.25; + announce('Oynatma hızı ' + video.playbackRate + ' katına düşürüldü.'); + } + } + + // 3.1. Injects Accessible Player Action Bar under the Watch Video + function injectAccessiblePlayerBar() { + if (!window.location.pathname.includes('/videos/watch/')) return; + var video = getVideoElement(); + if (!video) return; + + var targetContainer = document.querySelector( + 'my-video-watch, .video-watch, .watch-container, #yaktube-ondemand-container' + ); + if (!targetContainer) return; + + var existingBar = document.getElementById('yaktube-player-action-bar'); + if (existingBar) return; + + var bar = document.createElement('div'); + bar.id = 'yaktube-player-action-bar'; + bar.className = 'yaktube-player-action-bar'; + bar.setAttribute('role', 'toolbar'); + bar.setAttribute('aria-label', 'Erişilebilir Video Kontrolleri'); + + bar.innerHTML = + '⚡ Oynatıcı Kontrolleri:' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + + var playerWrap = + document.querySelector('.player-container, .video-player-container, .player-wrapper, my-video-watch .player') || + targetContainer; + if (playerWrap && playerWrap.nextSibling) { + playerWrap.parentNode.insertBefore(bar, playerWrap.nextSibling); + } else { + targetContainer.prepend(bar); + } + + // Attach button actions + bar.querySelector('#yaktube-bar-rewind-btn').addEventListener('click', function () { + if (video) { + video.currentTime = Math.max(0, video.currentTime - 10); + announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); + } + }); + + bar.querySelector('#yaktube-bar-play-btn').addEventListener('click', function () { + if (video) { + if (video.paused) { + video.play(); + announce('Video oynatılıyor.'); + } else { + video.pause(); + announce('Video duraklatıldı.'); + } + } + }); + + bar.querySelector('#yaktube-bar-forward-btn').addEventListener('click', function () { + if (video) { + video.currentTime = Math.min(video.duration || 99999, video.currentTime + 10); + announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); + } + }); + + bar.querySelector('#yaktube-bar-speed-btn').addEventListener('click', function () { + adjustPlaybackSpeed(true); + }); + + bar.querySelector('#yaktube-bar-theater-btn').addEventListener('click', function () { + document.body.classList.toggle('yaktube-theater-mode'); + var isT = document.body.classList.contains('yaktube-theater-mode'); + announce(isT ? 'Sinema modu açıldı.' : 'Sinema modundan çıkıldı.'); + }); + + bar.querySelector('#yaktube-bar-info-btn').addEventListener('click', function () { + showQuickVideoInfo(); + }); + + bar.querySelector('#yaktube-bar-help-btn').addEventListener('click', function () { + toggleShortcutsModal(); + }); + } + + // 4. Global Keydown Listener (Screen-Reader & Turkish Q Optimized) window.addEventListener('keydown', function (e) { if (e.key === 'Escape') { var shortcutsModal = document.getElementById('yaktube-shortcuts-dialog'); @@ -747,7 +852,8 @@ return; } - if (e.key === '?' || (e.shiftKey && e.key === '/')) { + // 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(); @@ -755,6 +861,28 @@ } } + // 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; } @@ -763,9 +891,10 @@ if (!video) return; var key = e.key.toLowerCase(); + var alt = e.altKey; - // Play / Pause (Space / K) - if (key === ' ' || key === 'k') { + // Play / Pause (Alt+K, Space or K) + if (key === ' ' || key === 'k' || (alt && key === 'k')) { e.preventDefault(); if (video.paused) { video.play(); @@ -775,44 +904,44 @@ announce('Video duraklatıldı.'); } } - // Seek 10s Back / Forward (J / L) - else if (key === 'j') { + // 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') { + } 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 (Left / Right Arrow) - else if (e.key === 'ArrowLeft') { + // 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') { + } 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 (Up / Down Arrow) - else if (e.key === 'ArrowUp') { + // 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 + 0.05); + 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') { + } else if (e.key === 'ArrowDown' || (alt && e.key === 'ArrowDown')) { e.preventDefault(); - video.volume = Math.max(0, video.volume - 0.05); + video.volume = Math.max(0, (video.volume || 1) - 0.05); announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100)); } - // Mute (M) - else if (key === 'm') { + // 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 (F) - else if (key === 'f') { + // 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; @@ -823,20 +952,20 @@ announce('Tam ekrandan çıkıldı.'); } } - // Theater Mode (T) - else if (key === 't') { + // 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 (I) - else if (key === 'i') { + // Quick Video Info (Alt+I or I) + else if (key === 'i' || (alt && key === 'i')) { e.preventDefault(); showQuickVideoInfo(); } - // Subtitles (C) - else if (key === 'c') { + // Subtitles (Alt+C or C) + else if (key === 'c' || (alt && key === 'c')) { e.preventDefault(); var tracks = video.textTracks; if (tracks && tracks.length > 0) { @@ -847,31 +976,13 @@ announce('Bu videoda altyazı bulunmuyor.'); } } - // Speed Controls (< and > / Shift+, and Shift+.) - else if (e.key === '>' || (e.shiftKey && e.key === '.')) { + // Speed Controls (Alt+. / Alt+, or > / <) + else if ((alt && (e.key === '.' || e.key === '>')) || e.key === '>') { e.preventDefault(); - var curSpeed = video.playbackRate || 1; - var nextIdx = speedSteps.findIndex(function (s) { - return s > curSpeed; - }); - if (nextIdx !== -1) { - video.playbackRate = speedSteps[nextIdx]; - } else { - video.playbackRate = 2; - } - announce('Oynatma hızı ' + video.playbackRate + ' katına çıkarıldı.'); - } else if (e.key === '<' || (e.shiftKey && e.key === ',')) { + adjustPlaybackSpeed(true); + } else if ((alt && (e.key === ',' || e.key === '<')) || e.key === '<') { e.preventDefault(); - var curSpeed = video.playbackRate || 1; - var prevSteps = speedSteps.filter(function (s) { - return s < curSpeed; - }); - if (prevSteps.length > 0) { - video.playbackRate = prevSteps[prevSteps.length - 1]; - } else { - video.playbackRate = 0.25; - } - announce('Oynatma hızı ' + video.playbackRate + ' katına düşürüldü.'); + adjustPlaybackSpeed(false); } // Percentage Jumps (0 - 9) else if (key >= '0' && key <= '9' && !e.ctrlKey && !e.altKey && !e.metaKey) { diff --git a/package.json b/package.json index e9f36ce..024a2a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peertube-plugin-yaktube-ondemand", - "version": "1.4.0", + "version": "1.4.1", "description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations and A11y Engine for PeerTube", "main": "main.js", "clientScripts": [ diff --git a/style.css b/style.css index d09c933..152b356 100644 --- a/style.css +++ b/style.css @@ -843,3 +843,54 @@ body.yaktube-theater-mode .video-js { .yaktube-info-toast-row b { color: #fff !important; } + +/* -------------------------------------------------------------------------- */ +/* ACCESSIBLE PLAYER ACTION BAR & SHORTCUTS (v1.4.1) */ +/* -------------------------------------------------------------------------- */ + +.yaktube-player-action-bar { + display: flex !important; + flex-wrap: wrap !important; + gap: 8px !important; + align-items: center !important; + background: #181820 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 12px !important; + padding: 10px 14px !important; + margin: 12px 0 16px 0 !important; + width: 100% !important; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4) !important; +} + +.yaktube-player-bar-label { + font-size: 13px !important; + font-weight: 700 !important; + color: #ff8f37 !important; + margin-right: 6px !important; + display: flex !important; + align-items: center !important; + gap: 4px !important; +} + +.yaktube-player-action-btn { + background: #252530 !important; + color: #f1f1f1 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 8px !important; + padding: 6px 12px !important; + font-size: 13px !important; + font-weight: 600 !important; + cursor: pointer !important; + display: inline-flex !important; + align-items: center !important; + gap: 6px !important; + transition: all 0.2s ease !important; +} + +.yaktube-player-action-btn:hover, +.yaktube-player-action-btn:focus { + background: rgba(255, 143, 55, 0.2) !important; + border-color: #ff8f37 !important; + color: #fff !important; + outline: none !important; +}