diff --git a/README.md b/README.md index f9a537f..9d3ab3f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A modern, fast, and fully-featured YouTube-like video streaming platform. Built - **Watch History & Suggestions**: Keep track of what you've watched seamlessly! Fully integrated library history tracking. - **Subscriptions Management**: Keep up to date with seamless subscription updates for YouTube channels. - **Optimized for Safari**: Stutter-free playback algorithms and high-tolerance Hls.js configurations tailored for macOS users. +- **Background Audio**: Allows videos to continue playing audio when the browser tab is hidden or device locked (perfect for music). - **Progressive Web App**: Fully installable PWA out of the box with offline fallbacks and custom vector iconography. - **Region Selection**: Tailor your content to specific regions (e.g., Vietnam). - **Responsive Design**: Beautiful, mobile-friendly interface with light and dark theme support. @@ -37,7 +38,7 @@ version: '3.8' services: kv-tube-app: - image: git.khoavo.myds.me/vndangkhoa/kv-tube-app:v4.0.3 + image: git.khoavo.myds.me/vndangkhoa/kv-tube-app:v4.0.4 container_name: kv-tube-app restart: unless-stopped ports: diff --git a/docker-compose.yml b/docker-compose.yml index 032f96b..39d2d86 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ version: '3.8' services: kv-tube-app: - image: git.khoavo.myds.me/vndangkhoa/kv-tube-app:v4.0.3 + image: git.khoavo.myds.me/vndangkhoa/kv-tube-app:v4.0.4 container_name: kv-tube-app platform: linux/amd64 restart: unless-stopped diff --git a/frontend/app/watch/VideoPlayer.tsx b/frontend/app/watch/VideoPlayer.tsx index 1db02f2..d389575 100755 --- a/frontend/app/watch/VideoPlayer.tsx +++ b/frontend/app/watch/VideoPlayer.tsx @@ -97,13 +97,18 @@ export default function VideoPlayer({ videoId, title }: VideoPlayerProps) { const audio = audioRef.current; if (!video || !audio || !hasSeparateAudio) return; - // Relax the tolerance to 0.4s to prevent choppy audio resetting on Safari + const isHidden = document.visibilityState === 'hidden'; + if (Math.abs(video.currentTime - audio.currentTime) > 0.4) { - audio.currentTime = video.currentTime; + if (!isHidden || !video.paused) { + audio.currentTime = video.currentTime; + } } if (video.paused && !audio.paused) { - audio.pause(); + if (!isHidden) { + audio.pause(); + } } else if (!video.paused && audio.paused) { audio.play().catch(() => { }); } @@ -189,10 +194,21 @@ export default function VideoPlayer({ videoId, title }: VideoPlayerProps) { video.addEventListener(event, handler); }); + const handleVisibilityChange = () => { + if (document.visibilityState === 'visible' && video && audioRef.current && hasSeparateAudio) { + if (video.paused && !audioRef.current.paused) { + video.currentTime = audioRef.current.currentTime; + video.play().catch(() => { }); + } + } + }; + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => { Object.entries(handlers).forEach(([event, handler]) => { video.removeEventListener(event, handler); }); + document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, [hasSeparateAudio]); @@ -210,6 +226,24 @@ export default function VideoPlayer({ videoId, title }: VideoPlayerProps) { const handleWaiting = () => setIsBuffering(true); const handleLoadStart = () => setIsLoading(true); + if ('mediaSession' in navigator) { + navigator.mediaSession.metadata = new MediaMetadata({ + title: title || 'KV-Tube Video', + artist: 'KV-Tube', + artwork: [ + { src: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`, sizes: '480x360', type: 'image/jpeg' } + ] + }); + navigator.mediaSession.setActionHandler('play', () => { + video.play().catch(() => { }); + if (needsSeparateAudio && audioRef.current) audioRef.current.play().catch(() => { }); + }); + navigator.mediaSession.setActionHandler('pause', () => { + video.pause(); + if (needsSeparateAudio && audioRef.current) audioRef.current.pause(); + }); + } + video.addEventListener('canplay', handleCanPlay); video.addEventListener('playing', handlePlaying); video.addEventListener('waiting', handleWaiting); diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png index 82e74ec..8978f07 100644 Binary files a/frontend/public/apple-touch-icon.png and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/icon-192x192.png b/frontend/public/icon-192x192.png index 0f36314..4121ef2 100644 Binary files a/frontend/public/icon-192x192.png and b/frontend/public/icon-192x192.png differ diff --git a/frontend/public/icon-512x512.png b/frontend/public/icon-512x512.png index 2c9f9c9..649f657 100644 Binary files a/frontend/public/icon-512x512.png and b/frontend/public/icon-512x512.png differ diff --git a/frontend/public/kv-tube-logo.png b/frontend/public/kv-tube-logo.png index 47235c6..32c366a 100644 Binary files a/frontend/public/kv-tube-logo.png and b/frontend/public/kv-tube-logo.png differ