feat: Add external player options to VideoActionButtons

This commit is contained in:
Peifan Li
2025-12-16 22:01:49 -05:00
parent 22d625bd37
commit b57e9df2ce
2 changed files with 134 additions and 5 deletions

View File

@@ -1,12 +1,11 @@
import { Add, Delete, Share } from '@mui/icons-material';
import { Button, Stack, Tooltip } from '@mui/material';
import React from 'react';
import { Add, Cast, Delete, Share } from '@mui/icons-material';
import { Button, Menu, MenuItem, Stack, Tooltip } from '@mui/material';
import React, { useState } from 'react';
import { useLanguage } from '../../../contexts/LanguageContext';
import { useSnackbar } from '../../../contexts/SnackbarContext';
import { useShareVideo } from '../../../hooks/useShareVideo';
import { Video } from '../../../types';
interface VideoActionButtonsProps {
video: Video;
onAddToCollection: () => void;
@@ -22,10 +21,139 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
}) => {
const { t } = useLanguage();
const { handleShare } = useShareVideo(video);
const { showSnackbar } = useSnackbar();
const [playerMenuAnchor, setPlayerMenuAnchor] = useState<null | HTMLElement>(null);
const getVideoUrl = (): string => {
if (video.videoPath) {
const videoPath = video.videoPath.startsWith('/') ? video.videoPath : `/${video.videoPath}`;
// Always use current origin for external players to ensure accessibility
// The browser's same-origin policy means videos are served from the same origin
// when accessed remotely, so window.location.origin is the correct base URL
return `${window.location.origin}${videoPath}`;
}
return video.sourceUrl;
};
const handlePlayerMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setPlayerMenuAnchor(event.currentTarget);
};
const handlePlayerMenuClose = () => {
setPlayerMenuAnchor(null);
};
const handlePlayerSelect = (player: string) => {
const videoUrl = getVideoUrl();
try {
let playerUrl = '';
switch (player) {
case 'vlc':
playerUrl = `vlc://${videoUrl}`;
break;
case 'iina':
playerUrl = `iina://weblink?url=${encodeURIComponent(videoUrl)}`;
break;
case 'mpv':
playerUrl = `mpv://${videoUrl}`;
break;
case 'potplayer':
playerUrl = `potplayer://${videoUrl}`;
break;
case 'infuse':
playerUrl = `infuse://x-callback-url/play?url=${encodeURIComponent(videoUrl)}`;
break;
case 'copy':
// Copy URL to clipboard
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(videoUrl).then(() => {
showSnackbar(t('linkCopied'), 'success');
}).catch(() => {
showSnackbar(t('copyFailed'), 'error');
});
} else {
// Fallback
const textArea = document.createElement("textarea");
textArea.value = videoUrl;
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showSnackbar(t('linkCopied'), 'success');
} else {
showSnackbar(t('copyFailed'), 'error');
}
} catch (err) {
showSnackbar(t('copyFailed'), 'error');
}
document.body.removeChild(textArea);
}
handlePlayerMenuClose();
return;
default:
return;
}
// Try to open the player URL using a hidden anchor element
// This prevents navigation away from the page
const link = document.createElement('a');
link.href = playerUrl;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Show a message after a short delay
setTimeout(() => {
showSnackbar(t('openInExternalPlayer'), 'info');
}, 500);
} catch (error) {
console.error('Error opening player:', error);
showSnackbar(t('copyFailed'), 'error');
}
handlePlayerMenuClose();
};
return (
<Stack direction="row" spacing={1}>
<Tooltip title={t('playWith')}>
<Button
variant="outlined"
color="inherit"
onClick={handlePlayerMenuOpen}
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'primary.main', borderColor: 'primary.main' } }}
>
<Cast />
</Button>
</Tooltip>
<Menu
anchorEl={playerMenuAnchor}
open={Boolean(playerMenuAnchor)}
onClose={handlePlayerMenuClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<MenuItem onClick={() => handlePlayerSelect('vlc')}>VLC</MenuItem>
<MenuItem onClick={() => handlePlayerSelect('iina')}>IINA</MenuItem>
<MenuItem onClick={() => handlePlayerSelect('mpv')}>mpv</MenuItem>
<MenuItem onClick={() => handlePlayerSelect('potplayer')}>PotPlayer</MenuItem>
<MenuItem onClick={() => handlePlayerSelect('infuse')}>Infuse</MenuItem>
<MenuItem onClick={() => handlePlayerSelect('copy')}>{t('copyUrl')}</MenuItem>
</Menu>
<Tooltip title={t('share')}>
<Button

View File

@@ -239,6 +239,7 @@ export const en = {
tooManyAttempts: "Too many failed attempts.",
linkCopied: "Link copied to clipboard",
copyFailed: "Failed to copy link",
copyUrl: "Copy URL",
// Collection Page
loadingCollection: "Loading collection...",