|
|
- <?php
- ?>
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
- <meta name="mobile-web-app-capable" content="yes">
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
- <title>Hello Girl</title>
-
- <link rel="stylesheet">
-
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- html, body {
- width: 100%;
- height: 100%;
- min-height: 100vh;
- margin: 0;
- padding: 0;
- display: -webkit-box;
- display: -webkit-flex;
- display: flex;
- overflow: hidden;
- background-color: #000;
- color: #fff;
- font-family: -apple-system, BlinkMacSystemFont, Arial, sans-serif;
- }
- .video-container {
- position: relative;
- width: 100vw;
- height: 100vh;
- overflow: hidden;
- z-index: 1;
- }
- .video-element {
- position: absolute;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- object-fit: contain;
- background-color: #000;
- z-index: 1;
- transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94),
- opacity 0.3s ease;
- opacity: 0;
- pointer-events: none;
- }
- .video-element.active {
- opacity: 1;
- pointer-events: auto;
- transform: translateY(0);
- }
- .video-element.loading {
- opacity: 0.6;
- }
- .video-element.slide-up {
- transform: translateY(-100%);
- }
- .video-element.slide-up-enter {
- transform: translateY(100%);
- }
- .video-element.slide-down {
- transform: translateY(100%);
- }
- .video-element.slide-down-enter {
- transform: translateY(-100%);
- }
- .slide-mask {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: #000;
- z-index: 10;
- pointer-events: none;
- opacity: 0;
- }
- .slide-mask.slide-up-mask {
- animation: slideUp 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
- }
- .slide-mask.slide-down-mask {
- animation: slideDown 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
- }
- @keyframes slideUp {
- 0% {
- transform: translateY(100%);
- opacity: 1;
- }
- 50% {
- transform: translateY(0);
- opacity: 1;
- }
- 100% {
- transform: translateY(-100%);
- opacity: 1;
- }
- }
- @keyframes slideDown {
- 0% {
- transform: translateY(-100%);
- opacity: 1;
- }
- 50% {
- transform: translateY(0);
- opacity: 1;
- }
- 100% {
- transform: translateY(100%);
- opacity: 1;
- }
- }
- .toast-box {
- position: fixed;
- top: 1rem;
- left: 50%;
- transform: translateX(-50%);
- padding: 0.5rem 1rem;
- background-color: rgba(0, 0, 0, 0.7);
- backdrop-filter: blur(8px);
- border-radius: 4px;
- color: white;
- font-size: 0.9rem;
- z-index: 15;
- opacity: 0;
- transition: opacity 0.3s ease;
- pointer-events: none;
- }
- .toast-box.visible {
- opacity: 1;
- }
- .controls-group {
- position: fixed;
- top: 55%;
- right: 1rem;
- transform: translateY(-50%);
- display: flex;
- flex-direction: column;
- gap: 1.5rem;
- z-index: 20;
- }
- .control-button {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.2);
- backdrop-filter: blur(8px);
- display: flex;
- justify-content: center;
- align-items: center;
- color: white;
- font-size: 1.2rem;
- cursor: pointer;
- border: none;
- transition: background-color 0.3s ease;
- }
- .control-button:hover {
- background-color: rgba(255, 255, 255, 0.3);
- }
- .mute-button.muted {
- background-color: rgba(254, 44, 85, 0.6);
- }
- .loading-screen {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: #000;
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 100;
- }
- .loading-spinner {
- width: 50px;
- height: 50px;
- border: 3px solid rgba(255, 255, 255, 0.3);
- border-radius: 50%;
- border-top-color: #fe2c55;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- to { transform: rotate(360deg); }
- }
- .volume-hint {
- position: absolute;
- right: 10px;
- top: 50%;
- transform: translateY(-50%);
- color: rgba(255, 255, 255, 0.3);
- font-size: 12px;
- writing-mode: vertical-rl;
- z-index: 2;
- pointer-events: none;
- }
- @media (min-width: 769px) {
- .volume-hint {
- display: none;
- }
- }
- @media (max-width: 768px) {
- .vjs-progress-control:active .vjs-progress-holder {
- height: 8px !important;
- transition: height 0.2s ease;
- }
- }
- .progress-bar-2 {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- height: 3px;
- background-color: rgba(255, 255, 255, 0.2);
- z-index: 20;
- pointer-events: auto;
- transition: height 0.6s ease, background-color 0.6s ease;
- }
- .progress-bar-2::before {
- content: '';
- position: absolute;
- top: -87px;
- left: 0;
- right: 0;
- height: 90px;
- background-color: transparent;
- pointer-events: auto;
- }
- .progress-bar-2.expanded {
- height: 50px;
- background-color: rgba(0, 0, 0, 0.6);
- backdrop-filter: blur(6px);
- }
- .progress-bar-2-filled {
- height: 100%;
- background-color: #fe2c55;
- width: 0%;
- }
- .progress-bar-2-time {
- position: absolute;
- right: 10px;
- top: 5px;
- color: white;
- font-size: 1rem;
- text-shadow: 0 1px 2px rgba(0,0,0,0.8);
- background-color: rgba(0,0,0,0.5);
- padding: 4px 8px;
- border-radius: 3px;
- display: none;
- }
- .progress-bar-2.expanded .progress-bar-2-time {
- display: block;
- }
- .video-container.temp-progress-visible .progress-bar-2 {
- opacity: 0;
- pointer-events: none;
- }
- .bottom-click-area {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- height: 15vh;
- z-index: 15;
- pointer-events: auto;
- background-color: transparent;
- }
- </style>
- </head>
- <body>
- <div class="loading-screen" id="loadingScreen">
- <div class="loading-spinner"></div>
- </div>
- <div class="toast-box" id="toastBox"></div>
- <div class="video-container" id="videoContainer">
- <div class="slide-mask" id="slideMask"></div>
- <video id="video-current" class="video-element active" playsinline></video>
- <video id="video-next" class="video-element" playsinline></video>
-
- <div class="progress-bar-2" id="progressBar2">
- <div class="progress-bar-2-filled" id="progressBar2Filled"></div>
- <div class="progress-bar-2-time" id="progressBar2Time">00:00/00:00</div>
- </div>
-
- <div class="bottom-click-area" id="bottomClickArea"></div>
- </div>
- <div class="controls-group">
- <button class="control-button play-pause-button" id="playPauseButton">
- <i class="fa fa-pause"></i>
- </button>
- <button class="control-button mute-button" id="muteButton">
- <i class="fa fa-volume-up"></i>
- </button>
- <button class="control-button download-button" id="downloadButton">
- <i class="fa fa-download"></i>
- </button>
- <button class="control-button fullscreen-button" id="fullscreenButton">
- <i class="fa fa-expand"></i>
- </button>
- </div>
- <script>
- const API_SOURCES = [
- {
- name: 'xjj1',
- url: 'https://v2.xxapi.cn/api/meinv',
- parse: (data) => data.code === 200 && data.data ? data.data : null
- },
- {
- name: 'xjj2',
- url: 'https://api.codetabs.com/v1/敏感词XXX?quest=' + encodeURIComponent('https://api.mmp.cc/api/miss?type=json'),
- parse: (data) => data.status === 'success' && data.link ? data.link : null
- },
- {
- name: 'xjj3',
- url: 'https://api.kuleu.com/api/MP4_xiaojiejie?type=json',
- parse: (data) => data.code === 200 && data.mp4_video ? data.mp4_video : null
- }
- ];
- const MAX_HISTORY = 5;
- const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
- let videoHistory = [];
- let currentVideoUrl = '';
- let nextVideoUrl = '';
- let isPlaying = false;
- let isMuted = false;
- let isLoading = false;
- let isErrorHandling = false;
- let isVideoSwitching = false;
- let maxRetryAttempts = 3;
- let currentRetryCount = 0;
- let failedVideos = new Set();
- let isPreloadingNext = false;
- let currentApiIndex = 0;
- let allSourcesFailed = false;
- let controlsTimeout;
- const CONTROLS_HIDE_DELAY = 3000;
- const PROGRESS_STEP = 5;
- const VOLUME_STEP = 0.1;
- const TOAST_DURATION = 2000;
- const WHEEL_THROTTLE = 1000;
- let lastWheelTime = 0;
- let lastTapTime = 0;
- const DOUBLE_TAP_DELAY = 300;
- let savedVolume = 0.7;
- let savedMuted = false;
- let touchStartY = 0;
- let isDragging = false;
- let currentTransform = 0;
- const SWIPE_THRESHOLD = 50;
- let longPressTimer = null;
- const progressBar2 = document.getElementById('progressBar2');
- const progressBar2Filled = document.getElementById('progressBar2Filled');
- const progressBar2Time = document.getElementById('progressBar2Time');
- const bottomClickArea = document.getElementById('bottomClickArea');
- let isDraggingProgress = false;
- let touchStartCurrentTime = 0;
- const videoContainer = document.getElementById('videoContainer');
- const loadingScreen = document.getElementById('loadingScreen');
- const slideMask = document.getElementById('slideMask');
- let videoCurrent = document.getElementById('video-current');
- let videoNext = document.getElementById('video-next');
- const toastBox = document.getElementById('toastBox');
- const muteButton = document.getElementById('muteButton');
- const muteIcon = muteButton.querySelector('i');
- const downloadButton = document.getElementById('downloadButton');
- const fullscreenButton = document.getElementById('fullscreenButton');
- const fullscreenIcon = fullscreenButton.querySelector('i');
- const playPauseButton = document.getElementById('playPauseButton');
- const playPauseIcon = playPauseButton.querySelector('i');
- function showToast(message) {
- if (window.toastTimer) clearTimeout(window.toastTimer);
- toastBox.textContent = message;
- toastBox.classList.add('visible');
- window.toastTimer = setTimeout(() => {
- toastBox.classList.remove('visible');
- }, TOAST_DURATION);
- }
- function formatTime(seconds) {
- const mins = Math.floor(seconds / 60);
- const secs = Math.floor(seconds % 60);
- return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
- }
- function updateProgressBar2() {
- if (!videoCurrent || !videoCurrent.duration) return;
- const ratio = videoCurrent.currentTime / videoCurrent.duration;
- progressBar2Filled.style.width = `${ratio * 100}%`;
- progressBar2Time.textContent = `${formatTime(videoCurrent.currentTime)}/${formatTime(videoCurrent.duration)}`;
- }
- function handleBottomAreaClick(e) {
- if (!isMobile || !videoCurrent || !videoCurrent.duration) return;
- e.stopPropagation();
- const rect = videoContainer.getBoundingClientRect();
- const ratio = (e.clientX - rect.left) / rect.width;
- const prevTime = videoCurrent.currentTime;
- videoCurrent.currentTime = ratio * videoCurrent.duration;
- const newTime = videoCurrent.currentTime;
- const movedSeconds = Math.round(newTime - prevTime);
- if (movedSeconds !== 0) {
- const direction = movedSeconds > 0 ? '快进' : '快退';
- showToast(`${direction} ${Math.abs(movedSeconds)}秒`);
- }
- }
- async function fetchVideoUrl(retryCount = 0, apiIndex = 0) {
- const apiSource = API_SOURCES[apiIndex];
- const timeout = 5000;
-
- try {
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), timeout);
-
- const response = await fetch(apiSource.url, {
- signal: controller.signal
- });
-
- clearTimeout(timeoutId);
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const data = await response.json();
- const videoUrl = apiSource.parse(data);
-
- if (videoUrl) {
- currentApiIndex = apiIndex;
- console.log(`使用API源 ${apiSource.name} 获取视频成功`);
- return videoUrl;
- } else {
- throw new Error('API返回数据格式错误');
- }
- } catch (error) {
- console.error(`API源 ${apiSource.name} 请求失败:`, error.message);
-
- if (apiIndex < API_SOURCES.length - 1) {
- console.log(`切换到下一个API源: ${API_SOURCES[apiIndex + 1].name}`);
- return fetchVideoUrl(retryCount, apiIndex + 1);
- } else {
- if (retryCount < 3) {
- console.log(`所有API源都失败,重试第 ${retryCount + 1} 次`);
- await new Promise(resolve => setTimeout(resolve, 1000));
- return fetchVideoUrl(retryCount + 1, 0);
- } else {
- allSourcesFailed = true;
- showToast('所有源都不可用,尝试刷新页面或稍后访问');
- throw new Error('所有API源都不可用');
- }
- }
- }
- }
- function addToHistory(videoUrl) {
- if (currentVideoUrl) {
- videoHistory.push(currentVideoUrl);
- if (videoHistory.length > MAX_HISTORY) {
- videoHistory.shift();
- }
- }
- currentVideoUrl = videoUrl;
- }
- function getPreviousVideo() {
- if (videoHistory.length > 0) {
- const previousUrl = videoHistory.pop();
- currentVideoUrl = previousUrl;
- return previousUrl;
- }
- return null;
- }
- function playPreviousVideo() {
- if (isLoading) {
- console.log('正在加载中,跳过切换请求');
- return;
- }
- if (isErrorHandling) {
- console.log('正在处理错误,跳过切换请求');
- return;
- }
- const previousUrl = getPreviousVideo();
-
- if (previousUrl) {
- loadVideo(previousUrl, 'previous');
- showToast('返回上一个视频');
- } else {
- loadVideoFromAPI('previous');
- showToast('无更早历史,获取新视频');
- }
- }
- function playNextVideo() {
- if (isLoading) {
- console.log('正在加载中,跳过切换请求');
- return;
- }
- if (isErrorHandling) {
- console.log('正在处理错误,跳过切换请求');
- return;
- }
- if (nextVideoUrl) {
- addToHistory(nextVideoUrl);
- loadVideo(nextVideoUrl, 'next');
- showToast('切换下一个视频');
- } else {
- loadVideoFromAPI('next');
- showToast('获取新视频');
- }
- }
- async function loadVideoFromAPI(direction = 'none') {
- try {
- allSourcesFailed = false;
- const videoUrl = await fetchVideoUrl();
- if (videoUrl) {
- if (direction === 'next') {
- addToHistory(videoUrl);
- }
- loadVideo(videoUrl, direction);
- }
- } catch (error) {
- console.error('获取新视频失败:', error);
- if (allSourcesFailed) {
- showToast('所有源都不可用,尝试刷新页面或稍后访问');
- }
- }
- }
- function loadVideo(videoUrl, direction = 'none') {
- if (isLoading) {
- console.log('正在加载中,跳过重复请求');
- return;
- }
- if (isErrorHandling) {
- console.log('正在处理错误,跳过新的加载请求');
- return;
- }
- if (currentRetryCount >= maxRetryAttempts) {
- console.log('已达到最大重试次数:', currentRetryCount);
- showToast('视频加载失败次数过多,请刷新页面重试');
- loadingScreen.style.display = 'none';
- return;
- }
- isLoading = true;
- loadingScreen.style.display = 'flex';
- console.log('加载视频:', videoUrl, '重试次数:', currentRetryCount);
- if (direction === 'next') {
- videoCurrent.src = videoUrl;
- videoCurrent.load();
- videoCurrent.addEventListener('loadeddata', () => {
- console.log('视频加载成功 (next):', videoUrl);
- isVideoSwitching = true;
- slideMask.classList.add('slide-up-mask');
- setTimeout(() => {
- videoCurrent.muted = savedMuted;
- videoCurrent.volume = savedVolume;
- videoCurrent.play();
- loadingScreen.style.display = 'none';
- currentRetryCount = 0;
- failedVideos.clear();
- isLoading = false;
- isVideoSwitching = false;
- preloadNextVideo();
- }, 150);
- setTimeout(() => {
- slideMask.classList.remove('slide-up-mask');
- }, 300);
- }, { once: true });
- videoCurrent.addEventListener('error', (e) => {
- console.error('视频加载失败 (next):', videoUrl, e);
- if (!videoCurrent.src) {
- console.log('忽略空src的错误(这是正常的切换操作)');
- isLoading = false;
- return;
- }
- if (isErrorHandling) {
- console.log('错误处理已在进行中,跳过重复处理');
- return;
- }
- if (isVideoSwitching) {
- console.log('正在切换视频,忽略旧的video元素的error事件');
- return;
- }
- isErrorHandling = true;
- failedVideos.add(videoUrl);
- currentRetryCount++;
- isLoading = false;
- if (currentRetryCount >= maxRetryAttempts) {
- showToast('视频加载失败次数过多,请刷新页面重试');
- loadingScreen.style.display = 'none';
- isErrorHandling = false;
- return;
- }
- showToast('下一个视频加载失败,尝试其他视频');
- loadingScreen.style.display = 'none';
- setTimeout(() => {
- isErrorHandling = false;
- playNextVideo();
- }, 500);
- }, { once: true });
- } else if (direction === 'previous') {
- videoCurrent.src = videoUrl;
- videoCurrent.load();
- videoCurrent.addEventListener('loadeddata', () => {
- console.log('视频加载成功 (previous):', videoUrl);
- isVideoSwitching = true;
- slideMask.classList.add('slide-down-mask');
- setTimeout(() => {
- videoCurrent.muted = savedMuted;
- videoCurrent.volume = savedVolume;
- videoCurrent.play();
- loadingScreen.style.display = 'none';
- currentRetryCount = 0;
- failedVideos.clear();
- isLoading = false;
- isVideoSwitching = false;
- preloadNextVideo();
- }, 150);
- setTimeout(() => {
- slideMask.classList.remove('slide-down-mask');
- }, 300);
- }, { once: true });
- videoCurrent.addEventListener('error', (e) => {
- console.error('视频加载失败 (previous):', videoUrl, e);
- if (!videoCurrent.src) {
- console.log('忽略空src的错误(这是正常的切换操作)');
- isLoading = false;
- return;
- }
- if (isErrorHandling) {
- console.log('错误处理已在进行中,跳过重复处理');
- return;
- }
- if (isVideoSwitching) {
- console.log('正在切换视频,忽略旧的video元素的error事件');
- return;
- }
- isErrorHandling = true;
- failedVideos.add(videoUrl);
- currentRetryCount++;
- isLoading = false;
- if (currentRetryCount >= maxRetryAttempts) {
- showToast('视频加载失败次数过多,请刷新页面重试');
- loadingScreen.style.display = 'none';
- isErrorHandling = false;
- return;
- }
- showToast('上一个视频加载失败,尝试其他视频');
- loadingScreen.style.display = 'none';
- setTimeout(() => {
- isErrorHandling = false;
- playPreviousVideo();
- }, 500);
- }, { once: true });
- } else {
- videoCurrent.src = videoUrl;
- videoCurrent.load();
- videoCurrent.addEventListener('loadeddata', () => {
- console.log('视频加载成功 (initial):', videoUrl);
- videoCurrent.muted = savedMuted;
- videoCurrent.volume = savedVolume;
- videoCurrent.play();
- loadingScreen.style.display = 'none';
- currentRetryCount = 0;
- failedVideos.clear();
- isLoading = false;
- preloadNextVideo();
- }, { once: true });
- videoCurrent.addEventListener('error', (e) => {
- console.error('视频加载失败 (initial):', videoUrl, e);
- if (!videoCurrent.src) {
- console.log('忽略空src的错误(这是正常的切换操作)');
- isLoading = false;
- return;
- }
- if (isErrorHandling) {
- console.log('错误处理已在进行中,跳过重复处理');
- return;
- }
- if (isVideoSwitching) {
- console.log('正在切换视频,忽略旧的video元素的error事件');
- return;
- }
- isErrorHandling = true;
- failedVideos.add(videoUrl);
- currentRetryCount++;
- isLoading = false;
- if (currentRetryCount >= maxRetryAttempts) {
- showToast('视频加载失败次数过多,请刷新页面重试');
- loadingScreen.style.display = 'none';
- isErrorHandling = false;
- return;
- }
- showToast('视频加载失败,尝试其他视频');
- loadingScreen.style.display = 'none';
- setTimeout(() => {
- isErrorHandling = false;
- playNextVideo();
- }, 500);
- }, { once: true });
- }
- currentVideoUrl = videoUrl;
- }
- async function preloadNextVideo() {
- if (isPreloadingNext) return;
- isPreloadingNext = true;
- try {
- const nextApiIndex = currentApiIndex;
- nextVideoUrl = await fetchVideoUrl(0, nextApiIndex);
- if (nextVideoUrl) {
- videoNext.src = nextVideoUrl;
- videoNext.load();
- }
- } catch (error) {
- console.error('预加载视频失败:', error);
- } finally {
- isPreloadingNext = false;
- }
- }
- function initPlayer() {
- videoCurrent.muted = savedMuted;
- videoCurrent.volume = savedVolume;
- setTimeout(() => {
- savedMuted = videoCurrent.muted;
- updateMuteButtonState();
- }, 100);
- updateFullscreenButtonState();
- updatePlayPauseButtonState();
- videoContainer.classList.add(videoCurrent.paused ? 'paused' : 'playing');
- videoContainer.classList.remove(videoCurrent.paused ? 'playing' : 'paused');
- videoCurrent.addEventListener('loadedmetadata', () => {
- loadingScreen.style.display = 'none';
- updateProgressBar2();
- });
- videoCurrent.addEventListener('timeupdate', updateProgressBar2);
- videoCurrent.addEventListener('play', () => {
- videoContainer.classList.remove('paused');
- videoContainer.classList.add('playing');
- updatePlayPauseButtonState();
- showToast('播放中');
- });
- videoCurrent.addEventListener('pause', () => {
- videoContainer.classList.remove('playing');
- videoContainer.classList.add('paused');
- updatePlayPauseButtonState();
- showToast('已暂停');
- });
- videoCurrent.addEventListener('ended', () => {
- if (!isLoading && !isErrorHandling) {
- playNextVideo();
- }
- });
- setupTouchEvents();
- if (!isMobile) {
- setupDesktopWheelEvent();
- }
- }
- function setupDesktopWheelEvent() {
- videoContainer.addEventListener('wheel', (e) => {
- e.preventDefault();
- const currentTime = Date.now();
- if (currentTime - lastWheelTime < WHEEL_THROTTLE) {
- return;
- }
- lastWheelTime = currentTime;
- if (e.deltaY > 0) {
- playNextVideo();
- } else {
- playPreviousVideo();
- }
- }, { passive: false });
- }
- function updateMuteButtonState() {
- if (!videoCurrent) return;
- if (videoCurrent.muted) {
- muteIcon.className = 'fa fa-volume-off';
- muteButton.classList.add('muted');
- } else {
- muteIcon.className = 'fa fa-volume-up';
- muteButton.classList.remove('muted');
- }
- }
- function updateFullscreenButtonState() {
- if (document.fullscreenElement) {
- fullscreenIcon.className = 'fa fa-compress';
- } else {
- fullscreenIcon.className = 'fa fa-expand';
- }
- }
- function updatePlayPauseButtonState() {
- if (!videoCurrent) return;
- if (videoCurrent.paused) {
- playPauseIcon.className = 'fa fa-play';
- } else {
- playPauseIcon.className = 'fa fa-pause';
- }
- }
- function togglePlayPause() {
- if (!videoCurrent) return;
- if (videoCurrent.paused) {
- videoCurrent.play();
- showToast('播放');
- } else {
- videoCurrent.pause();
- showToast('暂停');
- }
- }
- function toggleMute() {
- if (!videoCurrent) return;
- if (videoCurrent.muted) {
- videoCurrent.muted = false;
- videoCurrent.volume = savedVolume;
- showToast('已取消静音');
- } else {
- savedVolume = videoCurrent.volume;
- videoCurrent.muted = true;
- showToast('已静音');
- }
- updateMuteButtonState();
- }
- function toggleFullscreen() {
- if (!document.fullscreenElement) {
- if (videoContainer.requestFullscreen) videoContainer.requestFullscreen();
- else if (videoContainer.webkitRequestFullscreen) videoContainer.webkitRequestFullscreen();
- else if (videoContainer.msRequestFullscreen) videoContainer.msRequestFullscreen();
- showToast('已进入全屏');
- } else {
- if (document.exitFullscreen) document.exitFullscreen();
- else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
- else if (document.msExitFullscreen) document.msExitFullscreen();
- showToast('已退出全屏');
- }
- updateFullscreenButtonState();
- }
- function downloadVideo() {
- if (!currentVideoUrl) {
- showToast('没有可下载的视频');
- return;
- }
- showToast('正在下载视频...');
-
- fetch(currentVideoUrl)
- .then(response => {
- if (!response.ok) {
- throw new Error('网络请求失败');
- }
- return response.blob();
- })
- .then(blob => {
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.download = `video_${Date.now()}.mp4`;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.URL.revokeObjectURL(url);
- showToast('下载成功');
- })
- .catch(error => {
- console.error('下载失败:', error);
- showToast('下载失败,请尝试右键保存');
- });
- }
- function setupTouchEvents() {
- let touchStartY = 0;
- let touchStartX = 0;
- let isVolumeAdjust = false;
- let isProgressAdjust = false;
- let isSwipeToChangeVideo = false;
- const SWIPE_VIDEO_THRESHOLD = 50;
- const SWIPE_PROGRESS_THRESHOLD = 20;
- const VOLUME_AREA_WIDTH = window.innerWidth * 0.3;
- let lastTouchX = 0;
- let lastTouchY = 0;
- videoContainer.addEventListener('touchstart', (e) => {
- if (e.touches.length !== 1) return;
- const touch = e.touches[0];
- touchStartY = touch.clientY;
- touchStartX = touch.clientX;
- lastTouchX = touch.clientX;
- lastTouchY = touch.clientY;
- isVolumeAdjust = false;
- isProgressAdjust = false;
- isSwipeToChangeVideo = false;
- touchStartCurrentTime = videoCurrent.currentTime;
- }, { passive: true });
- videoContainer.addEventListener('touchmove', (e) => {
- if (e.touches.length !== 1) return;
- e.preventDefault();
- const touch = e.touches[0];
- const touchY = touch.clientY;
- const touchX = touch.clientX;
- const deltaY = touchStartY - touchY;
- const deltaX = touchX - touchStartX;
- const absDeltaY = Math.abs(deltaY);
- const absDeltaX = Math.abs(deltaX);
- if (absDeltaY > absDeltaX) {
- isSwipeToChangeVideo = true;
- isVolumeAdjust = false;
- } else {
- if (absDeltaX > SWIPE_PROGRESS_THRESHOLD) {
- isProgressAdjust = true;
- isSwipeToChangeVideo = false;
- const swipeRatio = Math.abs(deltaX) / window.innerWidth;
- const stepCount = Math.floor(swipeRatio / 0.1);
- const totalStep = stepCount * PROGRESS_STEP;
- const direction = deltaX > 0 ? 1 : -1;
- const newTime = touchStartCurrentTime + (direction * totalStep);
- const clampedTime = Math.max(0, Math.min(videoCurrent.duration || 0, newTime));
- videoCurrent.currentTime = clampedTime;
- const movedSeconds = Math.round(clampedTime - touchStartCurrentTime);
- if (movedSeconds !== 0 && movedSeconds % PROGRESS_STEP === 0) {
- const dirText = movedSeconds > 0 ? '快进' : '快退';
- showToast(`${dirText} ${Math.abs(movedSeconds)}秒`);
- }
- }
- }
- lastTouchX = touchX;
- lastTouchY = touchY;
- }, { passive: false });
- videoContainer.addEventListener('touchend', (e) => {
- clearTimeout(longPressTimer);
- if (isSwipeToChangeVideo) {
- const touchEndY = e.changedTouches[0].clientY;
- const deltaY = touchStartY - touchEndY;
- if (deltaY > SWIPE_VIDEO_THRESHOLD) playNextVideo();
- else if (deltaY < -SWIPE_VIDEO_THRESHOLD) playPreviousVideo();
- }
- const currentTime = Date.now();
- const tapTimeDiff = currentTime - lastTapTime;
- if (tapTimeDiff < DOUBLE_TAP_DELAY && tapTimeDiff > 0) {
- const touchEndX = e.changedTouches[0].clientX;
- const screenWidth = window.innerWidth;
- const isLeftSide = touchEndX < screenWidth * 0.4;
- const isRightSide = touchEndX > screenWidth * 0.6;
- if (isLeftSide) {
- videoCurrent.currentTime = Math.max(0, videoCurrent.currentTime - PROGRESS_STEP);
- showToast(`快退 ${PROGRESS_STEP}秒`);
- } else if (isRightSide) {
- if (videoCurrent.duration) {
- videoCurrent.currentTime = Math.min(videoCurrent.duration, videoCurrent.currentTime + PROGRESS_STEP);
- showToast(`快进 ${PROGRESS_STEP}秒`);
- }
- } else {
- toggleFullscreen();
- }
- lastTapTime = 0;
- } else {
- lastTapTime = currentTime;
- }
- }, { passive: true });
- }
- function setupKeyboardEvents() {
- document.addEventListener('keydown', (e) => {
- switch (e.key) {
- case ' ':
- case 'k':
- e.preventDefault();
- togglePlayPause();
- break;
- case 'f':
- case 'F':
- e.preventDefault();
- toggleFullscreen();
- break;
- case 'm':
- case 'M':
- e.preventDefault();
- toggleMute();
- break;
- case 'ArrowLeft':
- e.preventDefault();
- videoCurrent.currentTime = Math.max(0, videoCurrent.currentTime - PROGRESS_STEP);
- showToast(`快退 ${PROGRESS_STEP}秒`);
- break;
- case 'ArrowRight':
- e.preventDefault();
- if (videoCurrent.duration) {
- videoCurrent.currentTime = Math.min(videoCurrent.duration, videoCurrent.currentTime + PROGRESS_STEP);
- showToast(`快进 ${PROGRESS_STEP}秒`);
- }
- break;
- case 'PageUp':
- e.preventDefault();
- playPreviousVideo();
- break;
- case 'PageDown':
- e.preventDefault();
- playNextVideo();
- break;
- }
- });
- }
- function setupProgressBar2Click() {
- let isDraggingProgress = false;
- let touchStartX = 0;
- let touchStartTime = 0;
- let isTouchOperation = false;
- let isMouseOperation = false;
- progressBar2.addEventListener('click', (e) => {
- if (isTouchOperation || isMouseOperation || !videoCurrent.duration) return;
- const rect = progressBar2.getBoundingClientRect();
- const ratio = (e.clientX - rect.left) / rect.width;
- videoCurrent.currentTime = ratio * videoCurrent.duration;
- });
- progressBar2.addEventListener('touchstart', (e) => {
- if (e.touches.length !== 1) return;
- isTouchOperation = true;
- e.stopPropagation();
- const touch = e.touches[0];
- touchStartX = touch.clientX;
- touchStartTime = Date.now();
- isDraggingProgress = false;
- progressBar2.classList.add('expanded');
- isDraggingProgress = true;
- }, { passive: true });
- progressBar2.addEventListener('touchmove', (e) => {
- if (!isDraggingProgress || !videoCurrent.duration) return;
- e.preventDefault();
- e.stopPropagation();
- const touch = e.touches[0];
- const rect = progressBar2.getBoundingClientRect();
- const ratio = (touch.clientX - rect.left) / rect.width;
- const clampedRatio = Math.max(0, Math.min(1, ratio));
- const newTime = clampedRatio * videoCurrent.duration;
- progressBar2Filled.style.width = `${clampedRatio * 100}%`;
- progressBar2Time.textContent = `${formatTime(newTime)}/${formatTime(videoCurrent.duration)}`;
- videoCurrent.currentTime = newTime;
- }, { passive: false });
- progressBar2.addEventListener('touchend', (e) => {
- e.stopPropagation();
- setTimeout(() => {
- isTouchOperation = false;
- }, 100);
- progressBar2.classList.remove('expanded');
- isDraggingProgress = false;
- });
- progressBar2.addEventListener('mouseover', (e) => {
- if (isTouchOperation) return;
- progressBar2.classList.add('expanded');
- });
- progressBar2.addEventListener('mouseout', (e) => {
- if (isTouchOperation || isDraggingProgress) return;
- progressBar2.classList.remove('expanded');
- });
- progressBar2.addEventListener('mousedown', (e) => {
- if (isTouchOperation || !videoCurrent.duration) return;
- isMouseOperation = true;
- isDraggingProgress = true;
- e.preventDefault();
- const rect = progressBar2.getBoundingClientRect();
- const ratio = (e.clientX - rect.left) / rect.width;
- const clampedRatio = Math.max(0, Math.min(1, ratio));
- const newTime = clampedRatio * videoCurrent.duration;
- progressBar2Filled.style.width = `${clampedRatio * 100}%`;
- progressBar2Time.textContent = `${formatTime(newTime)}/${formatTime(videoCurrent.duration)}`;
- videoCurrent.currentTime = newTime;
- });
- document.addEventListener('mousemove', (e) => {
- if (!isDraggingProgress || !isMouseOperation || !videoCurrent.duration) return;
- const rect = progressBar2.getBoundingClientRect();
- const ratio = (e.clientX - rect.left) / rect.width;
- const clampedRatio = Math.max(0, Math.min(1, ratio));
- const newTime = clampedRatio * videoCurrent.duration;
- progressBar2Filled.style.width = `${clampedRatio * 100}%`;
- progressBar2Time.textContent = `${formatTime(newTime)}/${formatTime(videoCurrent.duration)}`;
- videoCurrent.currentTime = newTime;
- });
- document.addEventListener('mouseup', (e) => {
- if (isMouseOperation) {
- setTimeout(() => {
- isMouseOperation = false;
- }, 100);
- isDraggingProgress = false;
- }
- });
- }
- function setupFullscreenEvents() {
- document.addEventListener('fullscreenchange', updateFullscreenButtonState);
- document.addEventListener('webkitfullscreenchange', updateFullscreenButtonState);
- document.addEventListener('msfullscreenchange', updateFullscreenButtonState);
- }
- function setupControlButtons() {
- playPauseButton.addEventListener('click', togglePlayPause);
- muteButton.addEventListener('click', toggleMute);
- downloadButton.addEventListener('click', downloadVideo);
- fullscreenButton.addEventListener('click', toggleFullscreen);
- videoContainer.addEventListener('dblclick', (e) => {
- e.preventDefault();
- toggleFullscreen();
- });
- }
- function initApp() {
- initPlayer();
- setupControlButtons();
- setupKeyboardEvents();
- setupProgressBar2Click();
- setupFullscreenEvents();
- loadVideoFromAPI();
- }
- document.addEventListener('DOMContentLoaded', initApp);
- </script>
- </body>
- </html>
复制代码
老规矩,贴出源码,论坛贴代码会吃掉cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css,建议还是去下载。 |
|