Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name YouTube Custom Controls
- // @namespace http://tampermonkey.net/
- // @version 5.5
- // @description Custom YouTube controls with movable panel
- // @author marek7400
- // @match *://*.youtube.com/*
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- // Globalne zmienne
- let currentZoom = 1;
- let panX = 0;
- let panY = 0;
- let isDragging = false;
- let hasDragged = false;
- let dragStartX = 0;
- let dragStartY = 0;
- let initialPanX = 0;
- let initialPanY = 0;
- let reqFrame = null;
- let dragInitDone = false;
- let transitionTimeout = null;
- let isTransitioning = false;
- // Wstrzykujemy style CSS do strony
- function addHoverStyles() {
- if (document.getElementById('custom-yt-controls-style')) return;
- const style = document.createElement('style');
- style.id = 'custom-yt-controls-style';
- style.textContent = `
- #custom-yt-controls {
- position: absolute;
- bottom: 63px; /* Domyślna pozycja */
- right: 25px; /* Domyślna pozycja */
- background-color: rgba(0, 0, 0, 0.7);
- backdrop-filter: blur(4px);
- border-radius: 10px;
- padding: 6px;
- display: flex;
- gap: 8px;
- z-index: 50;
- box-shadow: 0 2px 10px rgba(0,0,0,0.5);
- align-items: center;
- cursor: grab; /* Kursor chwytania dla całego panelu */
- opacity: 0;
- visibility: hidden;
- pointer-events: none;
- /* Usunięto transition dla 'bottom', aby przesuwanie myszką było płynne */
- transition: opacity 0.25s cubic-bezier(0.0,0.0,0.2,1), visibility 0.25s;
- }
- #custom-yt-controls:active {
- cursor: grabbing;
- }
- .html5-video-player:not(.ytp-autohide) #custom-yt-controls,
- #custom-yt-controls:hover,
- #custom-yt-controls.is-dragging {
- opacity: 1 !important;
- visibility: visible !important;
- pointer-events: auto;
- }
- /* Jeśli panel NIE ma nadpisanej pozycji, ułóż go wyżej w pełnym ekranie */
- .html5-video-player.ytp-fullscreen #custom-yt-controls:not([style*="top"]) {
- bottom: 140px;
- }
- .html5-video-player:has(.ytp-settings-button[aria-expanded="true"]) #custom-yt-controls {
- opacity: 0 !important;
- visibility: hidden !important;
- pointer-events: none !important;
- }
- .custom-yt-btn {
- background: transparent;
- border: none;
- color: #fff;
- cursor: pointer;
- padding: 6px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background-color 0.2s;
- width: 38px;
- height: 38px;
- outline: none;
- }
- .custom-yt-btn:hover {
- background-color: rgba(255, 255, 255, 0.25);
- }
- `;
- document.head.appendChild(style);
- }
- // --- System Powiadomień i Dźwięków ---
- function playSystemSound() {
- try {
- const AudioContext = window.AudioContext || window.webkitAudioContext;
- if (!AudioContext) return;
- const ctx = new AudioContext();
- function playTone(freq, startTime, duration, volume) {
- const osc = ctx.createOscillator();
- const gain = ctx.createGain();
- osc.type = 'sine';
- osc.frequency.setValueAtTime(freq, ctx.currentTime + startTime);
- gain.gain.setValueAtTime(0, ctx.currentTime + startTime);
- gain.gain.linearRampToValueAtTime(volume, ctx.currentTime + startTime + 0.02);
- gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + startTime + duration);
- osc.connect(gain);
- gain.connect(ctx.destination);
- osc.start(ctx.currentTime + startTime);
- osc.stop(ctx.currentTime + startTime + duration);
- }
- playTone(1200, 0, 0.08, 0.25);
- playTone(800, 0.05, 0.1, 0.25);
- } catch (e) { console.warn("AudioContext error", e); }
- }
- function showNotification(text) {
- const player = document.querySelector('.html5-video-player');
- if (!player) return;
- let notif = document.getElementById('custom-yt-notification');
- if (!notif) {
- notif = document.createElement('div');
- notif.id = 'custom-yt-notification';
- Object.assign(notif.style, {
- position: 'absolute',
- top: '50%',
- left: '50%',
- transform: 'translate(-50%, -50%)',
- backgroundColor: '#FFCC00',
- color: '#000000',
- padding: '16px 32px',
- borderRadius: '8px',
- fontSize: '22px',
- fontWeight: 'bold',
- zIndex: '999999',
- pointerEvents: 'none',
- opacity: '0',
- transition: 'opacity 0.3s ease-in-out',
- boxShadow: '0 6px 20px rgba(0,0,0,0.6)',
- fontFamily: '"Roboto", "Arial", sans-serif',
- textAlign: 'center',
- textTransform: 'uppercase'
- });
- player.appendChild(notif);
- }
- notif.textContent = text;
- notif.style.opacity = '1';
- if (notif.hideTimeout) clearTimeout(notif.hideTimeout);
- notif.hideTimeout = setTimeout(() => {
- notif.style.opacity = '0';
- }, 2000);
- }
- function onScreenshotSuccess() {
- playSystemSound();
- const player = document.querySelector('.html5-video-player');
- const isFullscreen = player && player.classList.contains('ytp-fullscreen');
- if (isFullscreen && currentZoom > 1) {
- showNotification('Zapisano zrzut w folderze pobrane');
- }
- }
- // --- System Przesuwania Obrazu (Pan/Zoom) ---
- function applyTransform(smooth = false) {
- if (reqFrame) cancelAnimationFrame(reqFrame);
- reqFrame = requestAnimationFrame(() => {
- reqFrame = null;
- const video = document.querySelector('video');
- const player = document.querySelector('.html5-video-player');
- if (!video || !player) return;
- video.style.transformOrigin = 'center center';
- if (smooth) {
- video.style.transition = 'transform 0.25s cubic-bezier(0.2, 0, 0.2, 1)';
- isTransitioning = true;
- if (transitionTimeout) clearTimeout(transitionTimeout);
- transitionTimeout = setTimeout(() => {
- isTransitioning = false;
- if (video) video.style.transition = 'none';
- }, 250);
- } else if (!isTransitioning) {
- video.style.transition = 'none';
- }
- if (currentZoom <= 1) {
- panX = 0;
- panY = 0;
- video.style.transform = `translate3d(0px, 0px, 0) scale(${currentZoom})`;
- return;
- }
- const rect = player.getBoundingClientRect();
- const maxTx = ((currentZoom - 1) * rect.width) / 2;
- const maxTy = ((currentZoom - 1) * rect.height) / 2;
- panX = Math.max(-maxTx, Math.min(maxTx, panX));
- panY = Math.max(-maxTy, Math.min(maxTy, panY));
- video.style.transform = `translate3d(${panX}px, ${panY}px, 0) scale(${currentZoom})`;
- });
- }
- function resetZoom() {
- currentZoom = 1;
- panX = 0;
- panY = 0;
- applyTransform(true);
- }
- function changeZoom(amount) {
- currentZoom += amount;
- currentZoom = Math.max(0.5, currentZoom);
- if (currentZoom <= 1) {
- panX = 0;
- panY = 0;
- }
- applyTransform(true);
- }
- function initDragPanning() {
- if (dragInitDone) return;
- dragInitDone = true;
- window.addEventListener('mousedown', (e) => {
- if (currentZoom <= 1 || e.button !== 0) return;
- const player = document.querySelector('.html5-video-player');
- if (!player || !player.contains(e.target)) return;
- if (e.target.closest('.ytp-chrome-bottom, .ytp-chrome-top, #custom-yt-controls, .ytp-settings-menu')) return;
- isDragging = true;
- hasDragged = false;
- dragStartX = e.clientX;
- dragStartY = e.clientY;
- initialPanX = panX;
- initialPanY = panY;
- player.style.cursor = 'grabbing';
- }, { capture: true });
- window.addEventListener('mousemove', (e) => {
- if (!isDragging) return;
- const dx = e.clientX - dragStartX;
- const dy = e.clientY - dragStartY;
- if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
- hasDragged = true;
- }
- panX = initialPanX + dx;
- panY = initialPanY + dy;
- applyTransform(false);
- }, { capture: true, passive: true });
- window.addEventListener('mouseup', () => {
- if (isDragging) {
- isDragging = false;
- const player = document.querySelector('.html5-video-player');
- if (player) player.style.cursor = '';
- }
- }, { capture: true });
- window.addEventListener('click', (e) => {
- if (hasDragged) {
- e.stopPropagation();
- e.preventDefault();
- hasDragged = false;
- }
- }, { capture: true });
- window.addEventListener('resize', () => {
- if (currentZoom > 1) applyTransform(false);
- });
- }
- // --- System Przesuwania Całego Panelu z Ikonami ---
- function initPanelDragging(panel) {
- let isDraggingPanel = false;
- let startX, startY, initialLeft, initialTop;
- panel.addEventListener('mousedown', (e) => {
- // Ignoruj kliknięcie, jeśli to kliknięcie w ikonę, a nie w tło panelu
- if (e.target.closest('.custom-yt-btn')) return;
- if (e.button !== 0) return; // Tylko lewy przycisk myszy
- e.preventDefault(); // Zapobiega zaznaczaniu tekstu
- e.stopPropagation(); // Zapobiega pauzowaniu wideo
- isDraggingPanel = true;
- panel.classList.add('is-dragging'); // Utrzymuje widoczność panelu podczas dragowania
- const player = document.querySelector('.html5-video-player');
- const playerRect = player.getBoundingClientRect();
- const panelRect = panel.getBoundingClientRect();
- startX = e.clientX;
- startY = e.clientY;
- // Pobieramy aktualną pozycję względem lewego górnego rogu playera
- initialLeft = panelRect.left - playerRect.left;
- initialTop = panelRect.top - playerRect.top;
- });
- document.addEventListener('mousemove', (e) => {
- if (!isDraggingPanel) return;
- const player = document.querySelector('.html5-video-player');
- if (!player) return;
- const playerRect = player.getBoundingClientRect();
- const dx = e.clientX - startX;
- const dy = e.clientY - startY;
- let newLeft = initialLeft + dx;
- let newTop = initialTop + dy;
- // Zabezpieczenie przed wyrzuceniem panelu poza ekran playera
- newLeft = Math.max(0, Math.min(newLeft, playerRect.width - panel.offsetWidth));
- newTop = Math.max(0, Math.min(newTop, playerRect.height - panel.offsetHeight));
- // Zapisujemy w PROCENTACH (aby działało przy zmianie na pełny ekran)
- const leftPercent = (newLeft / playerRect.width) * 100;
- const topPercent = (newTop / playerRect.height) * 100;
- panel.style.left = `${leftPercent}%`;
- panel.style.top = `${topPercent}%`;
- panel.style.right = 'auto'; // Reset domyślnych styli CSS
- panel.style.bottom = 'auto';
- });
- document.addEventListener('mouseup', () => {
- if (isDraggingPanel) {
- isDraggingPanel = false;
- panel.classList.remove('is-dragging');
- // Zapisujemy do LocalStorage, żeby pamiętało po odświeżeniu
- localStorage.setItem('ytCustomControlsPosition', JSON.stringify({
- left: panel.style.left,
- top: panel.style.top
- }));
- }
- });
- }
- // --- Narzędzia ---
- function simulateKey(key, keyCode, ctrlKey = false) {
- const target = document.querySelector('.html5-video-player') || document.body;
- const eventObj = {
- key: key,
- code: key === ',' ? 'Comma' : (key === '.' ? 'Period' : key),
- keyCode: keyCode,
- which: keyCode,
- ctrlKey: ctrlKey,
- bubbles: true,
- cancelable: true,
- composed: true
- };
- target.dispatchEvent(new KeyboardEvent('keydown', eventObj));
- target.dispatchEvent(new KeyboardEvent('keyup', eventObj));
- }
- function createIcon(pathData) {
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svg.setAttribute('viewBox', '0 0 24 24');
- svg.style.width = '24px';
- svg.style.height = '24px';
- svg.style.fill = '#ffffff';
- svg.style.display = 'block';
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- path.setAttribute('d', pathData);
- svg.appendChild(path);
- return svg;
- }
- const buttonsConfig =[
- {
- id: 'btn-screenshot',
- title: 'Zrób zrzut ekranu (lub pobierz miniaturkę na starcie)',
- path: 'M4 4h3l2-2h6l2 2h3c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zm8 3c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0 8c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z',
- action: async () => {
- const video = document.querySelector('video');
- if (!video) return;
- try {
- if (video.currentTime === 0) {
- const urlParams = new URLSearchParams(window.location.search);
- const videoId = urlParams.get('v');
- if (videoId) {
- const thumbUrls =[
- `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`,
- `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`
- ];
- let thumbDownloaded = false;
- for (const url of thumbUrls) {
- try {
- const response = await fetch(url);
- if (response.ok) {
- const blob = await response.blob();
- const blobUrl = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = blobUrl;
- a.download = `YouTube_Miniaturka_${videoId}.jpg`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(blobUrl);
- thumbDownloaded = true;
- break;
- }
- } catch (e) {
- console.warn('Nie udało się pobrać miniaturki.', e);
- }
- }
- if (thumbDownloaded) {
- onScreenshotSuccess();
- return;
- }
- }
- }
- if (video.videoWidth === 0 || video.videoHeight === 0) {
- alert('Obraz nie został załadowany. Odtwórz film na chwilę.');
- return;
- }
- const canvas = document.createElement('canvas');
- canvas.width = video.videoWidth;
- canvas.height = video.videoHeight;
- const ctx = canvas.getContext('2d');
- ctx.fillStyle = '#000000';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- if (currentZoom <= 1) {
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
- } else {
- const W = video.videoWidth;
- const H = video.videoHeight;
- const cssW = video.offsetWidth || W;
- const cssH = video.offsetHeight || H;
- const sW = W / currentZoom;
- const sH = H / currentZoom;
- const cx = (W / 2) - (panX / currentZoom) * (W / cssW);
- const cy = (H / 2) - (panY / currentZoom) * (H / cssH);
- const sx = cx - sW / 2;
- const sy = cy - sH / 2;
- ctx.drawImage(video, sx, sy, sW, sH, 0, 0, canvas.width, canvas.height);
- }
- const dataUrl = canvas.toDataURL('image/png');
- const time = video.currentTime;
- const hours = Math.floor(time / 3600);
- const minutes = Math.floor((time % 3600) / 60);
- const seconds = Math.floor(time % 60);
- const timeString = `${hours > 0 ? hours + 'h' : ''}${minutes}m${seconds}s`;
- const a = document.createElement('a');
- a.href = dataUrl;
- a.download = `YouTube_Zrzut_${timeString}.png`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- onScreenshotSuccess();
- } catch (err) {
- alert('Błąd - materiał może być chroniony DRM.');
- }
- }
- },
- {
- id: 'btn-zoom-in',
- title: 'Powiększ obraz (+50%)\nPrawy przycisk myszy: Reset na 100%',
- path: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z',
- action: () => changeZoom(0.5),
- actionContext: () => resetZoom()
- },
- {
- id: 'btn-zoom-out',
- title: 'Pomniejsz obraz (-50%)\nPrawy przycisk myszy: Reset na 100%',
- path: 'M19 13H5v-2h14v2z',
- action: () => changeZoom(-0.5),
- actionContext: () => resetZoom()
- },
- {
- id: 'btn-ctrl-left',
- title: 'Ctrl + Strzałka w lewo',
- path: 'M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z',
- action: () => { simulateKey('ArrowLeft', 37, true); }
- },
- {
- id: 'btn-comma',
- title: 'Klatka w tył (,)',
- path: 'M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z',
- action: () => {
- simulateKey(',', 188);
- const vid = document.querySelector('video');
- if (vid && vid.paused) { vid.currentTime -= 1/30; }
- }
- },
- {
- id: 'btn-dot',
- title: 'Klatka w przód (.)',
- path: 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z',
- action: () => {
- simulateKey('.', 190);
- const vid = document.querySelector('video');
- if (vid && vid.paused) { vid.currentTime += 1/30; }
- }
- },
- {
- id: 'btn-ctrl-right',
- title: 'Ctrl + Strzałka w prawo',
- path: 'M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z',
- action: () => { simulateKey('ArrowRight', 39, true); }
- }
- ];
- function checkAndCreateUI() {
- if (!window.location.pathname.startsWith('/watch')) return;
- addHoverStyles();
- initDragPanning();
- if (document.getElementById('custom-yt-controls')) return;
- const player = document.querySelector('.html5-video-player');
- if (!player) return;
- const container = document.createElement('div');
- container.id = 'custom-yt-controls';
- // Wczytywanie zapisanej pozycji z LocalStorage
- try {
- const savedPos = localStorage.getItem('ytCustomControlsPosition');
- if (savedPos) {
- const pos = JSON.parse(savedPos);
- if (pos.left && pos.top) {
- container.style.left = pos.left;
- container.style.top = pos.top;
- container.style.right = 'auto';
- container.style.bottom = 'auto';
- }
- }
- } catch (e) {
- console.warn('Nie udało się wczytać pozycji panelu', e);
- }
- buttonsConfig.forEach(config => {
- const btn = document.createElement('button');
- btn.id = config.id;
- btn.title = config.title;
- btn.className = 'custom-yt-btn';
- const iconSvg = createIcon(config.path);
- btn.appendChild(iconSvg);
- btn.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- config.action();
- });
- if (config.actionContext) {
- btn.addEventListener('contextmenu', (e) => {
- e.preventDefault();
- e.stopPropagation();
- config.actionContext();
- });
- }
- container.appendChild(btn);
- });
- player.appendChild(container);
- // Aktywuj system przesuwania dla nowo utworzonego panelu
- initPanelDragging(container);
- }
- setInterval(checkAndCreateUI, 1000);
- window.addEventListener('yt-navigate-start', () => {
- resetZoom();
- });
- })();
Add Comment
Please, Sign In to add comment