feat: Background audio via Media Session and Minimal App Icon; Bump v4.0.4
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
if (!isHidden || !video.paused) {
|
||||
audio.currentTime = video.currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (video.paused && !audio.paused) {
|
||||
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);
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 14 KiB |