diff --git a/client.js b/client.js index 4556fe6..33135b2 100644 --- a/client.js +++ b/client.js @@ -1849,15 +1849,102 @@ try { } } + function isWatchUrl() { + if (typeof window === 'undefined' || !window.location) return false; + var p = window.location.pathname; + return p.includes('/videos/watch/') || p.startsWith('/w/') || p.includes('/w/'); + } + + function injectControlBarSeekButtons(playerWrapper) { + var controlBar = (playerWrapper || document).querySelector('.vjs-control-bar'); + if (!controlBar) return; + + var rewindBtn = controlBar.querySelector('.yaktube-vjs-rewind-btn'); + var forwardBtn = controlBar.querySelector('.yaktube-vjs-forward-btn'); + var playControl = controlBar.querySelector('.vjs-play-control'); + + if (!rewindBtn) { + rewindBtn = document.createElement('button'); + rewindBtn.className = 'vjs-control vjs-button yaktube-vjs-seek-btn yaktube-vjs-rewind-btn'; + rewindBtn.type = 'button'; + rewindBtn.setAttribute('tabindex', '0'); + rewindBtn.setAttribute('aria-label', '10 saniye geri sar (Sol Ok veya J)'); + rewindBtn.setAttribute('title', '10 saniye geri sar (Alt+J)'); + rewindBtn.innerHTML = + '' + + '10 saniye geri sar'; + + rewindBtn.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + var video = getVideoElement(); + if (video) { + video.currentTime = Math.max(0, video.currentTime - 10); + announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); + showQuickToast('⏪ -10sn'); + } + }); + + if (playControl && playControl.nextSibling) { + controlBar.insertBefore(rewindBtn, playControl.nextSibling); + } else { + controlBar.appendChild(rewindBtn); + } + } + + if (!forwardBtn) { + forwardBtn = document.createElement('button'); + forwardBtn.className = 'vjs-control vjs-button yaktube-vjs-seek-btn yaktube-vjs-forward-btn'; + forwardBtn.type = 'button'; + forwardBtn.setAttribute('tabindex', '0'); + forwardBtn.setAttribute('aria-label', '10 saniye ileri sar (Sağ Ok veya L)'); + forwardBtn.setAttribute('title', '10 saniye ileri sar (Alt+L)'); + forwardBtn.innerHTML = + '' + + '10 saniye ileri sar'; + + forwardBtn.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + var video = getVideoElement(); + if (video) { + video.currentTime = Math.min(video.duration || 999999, video.currentTime + 10); + announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); + showQuickToast('⏩ +10sn'); + } + }); + + if (rewindBtn && rewindBtn.nextSibling) { + controlBar.insertBefore(forwardBtn, rewindBtn.nextSibling); + } else if (playControl && playControl.nextSibling) { + controlBar.insertBefore(forwardBtn, playControl.nextSibling); + } else { + controlBar.appendChild(forwardBtn); + } + } + } + function enhanceVideoPlayerAccessibility() { - var isWatchPage = window.location.pathname.includes('/videos/watch/'); + var isWatch = isWatchUrl(); var skipLink = document.getElementById('yaktube-skip-to-player'); - if (!isWatchPage) { + if (!isWatch) { if (skipLink) skipLink.remove(); return; } + // 0. Handle Video Alert accessibility & live announcement (e.g. transcoding/playback warnings) + var alertEl = document.querySelector('my-video-alert, .video-alert, .alert-warning, .vjs-error-display'); + if (alertEl && !alertEl.hasAttribute('data-yaktube-alert-handled')) { + alertEl.setAttribute('data-yaktube-alert-handled', 'true'); + alertEl.setAttribute('role', 'alert'); + alertEl.setAttribute('aria-live', 'assertive'); + var alertText = alertEl.textContent ? alertEl.textContent.trim().replace(/\s+/g, ' ') : ''; + if (alertText) { + announce('Video Uyarısı: ' + alertText, true); + } + } + // 1. Remove harmful role="application" from video and containers var videos = document.querySelectorAll('video, .vjs-tech'); videos.forEach(function (v) { @@ -2053,6 +2140,9 @@ try { }); } + // 4.1. Inject 10s Rewind & Forward buttons directly into Video.js control bar (Phones & Desktop) + injectControlBarSeekButtons(playerWrapper); + // Dynamic ARIA label updating with current title var curAriaLabel = 'Video Oynatıcı: ' + @@ -2069,12 +2159,10 @@ try { // 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; + if (!isWatchUrl()) return; var targetContainer = document.querySelector( - 'my-video-watch, .video-watch, .watch-container, #yaktube-ondemand-container' + 'my-video-watch, .video-watch, .watch-container, #yaktube-ondemand-container, .player-container, #video-wrapper, #videojs-wrapper' ); if (!targetContainer) return; @@ -2089,9 +2177,9 @@ try { bar.innerHTML = '⚡ Oynatıcı Kontrolleri:' + - '' + - '' + - '' + + '' + + '' + + '' + '' + '' + '' + @@ -2119,28 +2207,35 @@ try { // Attach button actions bar.querySelector('#yaktube-bar-rewind-btn').addEventListener('click', function () { + var video = getVideoElement(); if (video) { video.currentTime = Math.max(0, video.currentTime - 10); announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); + showQuickToast('⏪ -10sn'); } }); bar.querySelector('#yaktube-bar-play-btn').addEventListener('click', function () { + var video = getVideoElement(); if (video) { if (video.paused) { video.play(); announce('Video oynatılıyor.'); + showQuickToast('▶️ Oynatılıyor'); } else { video.pause(); announce('Video duraklatıldı.'); + showQuickToast('⏸️ Duraklatıldı'); } } }); bar.querySelector('#yaktube-bar-forward-btn').addEventListener('click', function () { + var video = getVideoElement(); if (video) { video.currentTime = Math.min(video.duration || 99999, video.currentTime + 10); announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); + showQuickToast('⏩ +10sn'); } }); diff --git a/package.json b/package.json index 44be317..7c528ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peertube-plugin-yaktube-ondemand", - "version": "1.5.4", + "version": "1.5.5", "description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations, Sleep Timer, Touch Gestures, Voice Assistant & A11y Engine for PeerTube", "main": "main.js", "library": "./main.js", diff --git a/style.css b/style.css index 0226651..b1ac4d4 100644 --- a/style.css +++ b/style.css @@ -1481,3 +1481,82 @@ my-date-toggle .date-toggle:focus-visible { outline: 2px solid #ff8f37 !important; outline-offset: 1px !important; } + +/* 6. Video.js Native Control Bar 10s Seek Buttons (Mobile & Desktop) */ +.video-js .vjs-control-bar .yaktube-vjs-seek-btn { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + width: 44px !important; + height: 100% !important; + min-width: 44px !important; + min-height: 44px !important; + cursor: pointer !important; + color: #f1f1f1 !important; + font-size: 13px !important; + font-weight: 700 !important; + position: relative !important; + user-select: none !important; + background: transparent !important; + border: none !important; + padding: 0 !important; + margin: 0 2px !important; + transition: + color 0.2s ease, + transform 0.15s ease !important; +} + +.video-js .vjs-control-bar .yaktube-vjs-seek-btn:hover, +.video-js .vjs-control-bar .yaktube-vjs-seek-btn:focus-visible { + color: #ff8f37 !important; + transform: scale(1.1) !important; + outline: 2px solid #ff8f37 !important; + outline-offset: -2px !important; +} + +.video-js .vjs-control-bar .yaktube-vjs-seek-btn:active { + transform: scale(0.92) !important; +} + +.yaktube-vjs-seek-icon { + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + gap: 1px !important; + font-size: 13px !important; + line-height: 1 !important; +} + +.yaktube-vjs-seek-num { + font-size: 10px !important; + font-weight: 800 !important; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + sans-serif !important; + color: #ff8f37 !important; + margin-top: 1px !important; +} + +/* 7. Mobile Watch Page & Action Bar Touch Optimizations */ +@media (max-width: 768px) { + .video-js .vjs-control-bar .yaktube-vjs-seek-btn { + width: 46px !important; + min-width: 46px !important; + font-size: 14px !important; + } + .yaktube-player-action-bar { + padding: 8px 10px !important; + gap: 6px !important; + } + .yaktube-player-action-btn { + padding: 9px 12px !important; + font-size: 12px !important; + flex: 1 1 calc(50% - 6px) !important; + justify-content: center !important; + min-height: 42px !important; + } +}