모바일 브라우저에서 좌우 스와이프 제스처로 동영상 탐색 기능 제공 (일반 video, Shadow DOM 포함)
目前為
// ==UserScript==
// @name Mobile Video Seek Gesture
// @namespace http://tampermonkey.net/
// @version 3.5
// @description 모바일 브라우저에서 좌우 스와이프 제스처로 동영상 탐색 기능 제공 (일반 video, Shadow DOM 포함)
// @author 사용자
// @license MIT
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
let startX = 0;
let initialTime = 0;
let seeking = false;
let timeChange = 0;
// 비디오별 오버레이 생성
function createOverlay(video) {
if (video.overlay) return; // 이미 오버레이가 있는 경우 리턴
let overlay = document.createElement('div');
overlay.style.position = 'absolute';
overlay.style.top = '50%';
overlay.style.left = '50%';
overlay.style.transform = 'translate(-50%, -50%)';
overlay.style.padding = '10px 20px';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
overlay.style.color = '#fff';
overlay.style.fontSize = '18px';
overlay.style.textAlign = 'center';
overlay.style.borderRadius = '8px';
overlay.style.zIndex = '9999';
overlay.style.display = 'none';
overlay.style.lineHeight = '1.5'; // 줄 간격 설정
video.parentElement.appendChild(overlay);
video.overlay = overlay; // 비디오에 오버레이 속성 추가
}
// 터치 시작 이벤트
function onTouchStart(e, video) {
if (!video) return;
startX = e.touches[0].clientX;
initialTime = video.currentTime;
seeking = true;
video.overlay.style.display = 'block';
}
// 터치 이동 이벤트
function onTouchMove(e, video) {
if (!seeking || !video) return;
let deltaX = e.touches[0].clientX - startX;
timeChange = deltaX * 0.05; // 민감도 값 조정
let newTime = initialTime + timeChange;
// 비디오 길이를 넘지 않도록 제한
let maxTime = video.duration;
if (newTime < 0) newTime = 0; // 최소 시간 0
if (newTime > maxTime) newTime = maxTime; // 최대 시간 비디오 길이로 제한
let timeChangeFormatted = formatTimeChange(timeChange);
video.overlay.innerHTML = `
<div>${formatCurrentTime(newTime)}</div>
<div>(${timeChange >= 0 ? '+' : ''}${timeChangeFormatted})</div>
`;
}
// 터치 종료 이벤트
function onTouchEnd(video) {
seeking = false;
let newTime = initialTime + timeChange;
// 비디오 길이를 넘지 않도록 제한
let maxTime = video.duration;
if (newTime < 0) newTime = 0;
if (newTime > maxTime) newTime = maxTime;
video.currentTime = newTime; // 변화된 시간 적용
video.overlay.style.display = 'none';
}
// 시간을 시:분:초 형식으로 변환
function formatCurrentTime(seconds) {
let absSeconds = Math.abs(seconds);
let hours = Math.floor(absSeconds / 3600);
let minutes = Math.floor((absSeconds % 3600) / 60);
let secs = Math.floor(absSeconds % 60);
if (hours > 0) {
return `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}:${secs < 10 ? '0' : ''}${secs}`;
} else {
return `${minutes < 10 ? '0' : ''}${minutes}:${secs < 10 ? '0' : ''}${secs}`;
}
}
// 시간 변화량을 형식화
function formatTimeChange(seconds) {
let sign = seconds < 0 ? '-' : ''; // 음수 표시
let absSeconds = Math.abs(seconds);
let hours = Math.floor(absSeconds / 3600);
let minutes = Math.floor((absSeconds % 3600) / 60);
let secs = Math.floor(absSeconds % 60);
let fraction = Math.round((absSeconds % 1) * 100);
if (absSeconds >= 3600) {
return `${sign}${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}:${secs < 10 ? '0' : ''}${secs}`;
} else if (absSeconds >= 60) {
return `${sign}${minutes < 10 ? '0' : ''}${minutes}:${secs < 10 ? '0' : ''}${secs}`;
} else {
return `${sign}${secs < 10 ? '0' : ''}${secs}.${fraction < 10 ? '0' : ''}${fraction}`;
}
}
// 비디오에 제스처 기능 추가
function addGestureControls(video) {
if (!video || video.hasAttribute('data-gesture-added')) return;
createOverlay(video);
video.addEventListener('touchstart', (e) => onTouchStart(e, video));
video.addEventListener('touchmove', (e) => onTouchMove(e, video));
video.addEventListener('touchend', () => onTouchEnd(video));
video.setAttribute('data-gesture-added', 'true');
}
// Shadow DOM 내 비디오 탐색
function findVideosInShadow(root) {
if (!root) return;
let videos = root.querySelectorAll('video');
videos.forEach(addGestureControls);
root.querySelectorAll('*').forEach(el => {
if (el.shadowRoot) findVideosInShadow(el.shadowRoot);
});
}
// 모든 비디오에 제스처 추가
function scanForVideos() {
document.querySelectorAll('video').forEach(addGestureControls);
document.querySelectorAll('*').forEach(el => {
if (el.shadowRoot) findVideosInShadow(el.shadowRoot);
});
}
// DOM 변경 감지 및 비디오 발견 시 제스처 추가
const observer = new MutationObserver(scanForVideos);
observer.observe(document.body, { childList: true, subtree: true });
// 페이지 로딩 시 비디오 탐색
window.addEventListener('load', scanForVideos);
})();