feat: Add external player integration for video playback
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
Folder,
|
||||
Link as LinkIcon,
|
||||
LocalOffer,
|
||||
PlayArrow,
|
||||
Share,
|
||||
VideoLibrary
|
||||
} from '@mui/icons-material';
|
||||
@@ -22,6 +23,9 @@ import {
|
||||
Button,
|
||||
Chip,
|
||||
Divider,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Rating,
|
||||
Stack,
|
||||
TextField,
|
||||
@@ -77,8 +81,11 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
|
||||
|
||||
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false);
|
||||
const [showDescriptionExpandButton, setShowDescriptionExpandButton] = useState(false);
|
||||
|
||||
const descriptionRef = useRef<HTMLParagraphElement>(null);
|
||||
|
||||
const [playerMenuAnchor, setPlayerMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const checkOverflow = () => {
|
||||
const element = titleRef.current;
|
||||
@@ -187,6 +194,42 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
const handleOpenPlayerMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setPlayerMenuAnchor(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClosePlayerMenu = () => {
|
||||
setPlayerMenuAnchor(null);
|
||||
};
|
||||
|
||||
const handlePlayInPlayer = (scheme: string) => {
|
||||
const videoUrl = `${BACKEND_URL}${video.videoPath || video.sourceUrl}`;
|
||||
let url = '';
|
||||
|
||||
switch (scheme) {
|
||||
case 'iina':
|
||||
url = `iina://weblink?url=${videoUrl}`;
|
||||
break;
|
||||
case 'vlc':
|
||||
url = `vlc://${videoUrl}`;
|
||||
break;
|
||||
case 'potplayer':
|
||||
url = `potplayer://${videoUrl}`;
|
||||
break;
|
||||
case 'mpv':
|
||||
url = `mpv://${videoUrl}`;
|
||||
break;
|
||||
case 'infuse':
|
||||
url = `infuse://x-callback-url/play?url=${videoUrl}`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
handleClosePlayerMenu();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{isEditingTitle ? (
|
||||
@@ -356,6 +399,43 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
|
||||
</Box>
|
||||
|
||||
<Stack direction="row" spacing={1}>
|
||||
<Tooltip title={t('openInExternalPlayer')}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
onClick={handleOpenPlayerMenu}
|
||||
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'primary.main', borderColor: 'primary.main' } }}
|
||||
>
|
||||
<PlayArrow />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
anchorEl={playerMenuAnchor}
|
||||
open={Boolean(playerMenuAnchor)}
|
||||
onClose={handleClosePlayerMenu}
|
||||
>
|
||||
<MenuItem disabled>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{t('playWith')}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={() => handlePlayInPlayer('iina')}>
|
||||
<ListItemText>IINA</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handlePlayInPlayer('vlc')}>
|
||||
<ListItemText>VLC</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handlePlayInPlayer('potplayer')}>
|
||||
<ListItemText>PotPlayer</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handlePlayInPlayer('mpv')}>
|
||||
<ListItemText>MPV</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handlePlayInPlayer('infuse')}>
|
||||
<ListItemText>Infuse</ListItemText>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Tooltip title={t('share')}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
|
||||
@@ -217,6 +217,8 @@ export const ar = {
|
||||
pleaseEnterSearchTerm: "الرجاء إدخال مصطلح البحث",
|
||||
failedToSearch: "فشل البحث. يرجى المحاولة مرة أخرى.",
|
||||
searchCancelled: "تم إلغاء البحث",
|
||||
openInExternalPlayer: "فتح في مشغل خارجي",
|
||||
playWith: "تشغيل بواسطة...",
|
||||
|
||||
// Login
|
||||
signIn: "تسجيل الدخول",
|
||||
|
||||
@@ -211,6 +211,8 @@ export const de = {
|
||||
pleaseEnterSearchTerm: "Bitte geben Sie einen Suchbegriff ein",
|
||||
failedToSearch: "Suche fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
searchCancelled: "Suche abgebrochen",
|
||||
openInExternalPlayer: "In externem Player öffnen",
|
||||
playWith: "Abspielen mit...",
|
||||
signIn: "Anmelden",
|
||||
verifying: "Überprüfen...",
|
||||
incorrectPassword: "Falsches Passwort",
|
||||
|
||||
@@ -216,6 +216,8 @@ export const en = {
|
||||
pleaseEnterSearchTerm: "Please enter a search term",
|
||||
failedToSearch: "Failed to search. Please try again.",
|
||||
searchCancelled: "Search was cancelled",
|
||||
openInExternalPlayer: "Open in external player",
|
||||
playWith: "Play with...",
|
||||
|
||||
// Login
|
||||
signIn: "Sign in",
|
||||
|
||||
@@ -226,6 +226,8 @@ export const es = {
|
||||
pleaseEnterSearchTerm: "Por favor, introduzca un término de búsqueda",
|
||||
failedToSearch: "Error en la búsqueda. Por favor, inténtelo de nuevo.",
|
||||
searchCancelled: "Búsqueda cancelada",
|
||||
openInExternalPlayer: "Abrir en reproductor externo",
|
||||
playWith: "Reproducir con...",
|
||||
signIn: "Iniciar Sesión",
|
||||
verifying: "Verificando...",
|
||||
incorrectPassword: "Contraseña incorrecta",
|
||||
|
||||
@@ -236,6 +236,8 @@ export const fr = {
|
||||
pleaseEnterSearchTerm: "Veuillez entrer un terme de recherche",
|
||||
failedToSearch: "Échec de la recherche. Veuillez réessayer.",
|
||||
searchCancelled: "Recherche annulée",
|
||||
openInExternalPlayer: "Ouvrir dans un lecteur externe",
|
||||
playWith: "Lire avec...",
|
||||
|
||||
// Login
|
||||
signIn: "Se connecter",
|
||||
|
||||
@@ -222,6 +222,8 @@ export const ja = {
|
||||
pleaseEnterSearchTerm: "検索語を入力してください",
|
||||
failedToSearch: "検索に失敗しました。もう一度お試しください。",
|
||||
searchCancelled: "検索がキャンセルされました",
|
||||
openInExternalPlayer: "外部プレーヤーで開く",
|
||||
playWith: "で再生...",
|
||||
|
||||
// Login
|
||||
signIn: "サインイン",
|
||||
|
||||
@@ -219,6 +219,8 @@ export const ko = {
|
||||
pleaseEnterSearchTerm: "검색어를 입력해주세요",
|
||||
failedToSearch: "검색 실패. 다시 시도해주세요.",
|
||||
searchCancelled: "검색이 취소되었습니다",
|
||||
openInExternalPlayer: "외부 플레이어에서 열기",
|
||||
playWith: "다음으로 재생...",
|
||||
|
||||
// Login
|
||||
signIn: "로그인",
|
||||
|
||||
@@ -231,6 +231,8 @@ export const pt = {
|
||||
pleaseEnterSearchTerm: "Por favor, insira um termo de pesquisa",
|
||||
failedToSearch: "Falha na pesquisa. Por favor, tente novamente.",
|
||||
searchCancelled: "Pesquisa cancelada",
|
||||
openInExternalPlayer: "Abrir no player externo",
|
||||
playWith: "Reproduzir com...",
|
||||
|
||||
// Login
|
||||
signIn: "Entrar",
|
||||
|
||||
@@ -223,6 +223,8 @@ export const ru = {
|
||||
pleaseEnterSearchTerm: "Пожалуйста, введите поисковый запрос",
|
||||
failedToSearch: "Поиск не удался. Пожалуйста, попробуйте снова.",
|
||||
searchCancelled: "Поиск отменен",
|
||||
openInExternalPlayer: "Открыть во внешнем плеере",
|
||||
playWith: "Воспроизвести с помощью...",
|
||||
|
||||
// Login
|
||||
signIn: "Войти",
|
||||
|
||||
@@ -211,6 +211,8 @@ export const zh = {
|
||||
pleaseEnterSearchTerm: "请输入搜索词",
|
||||
failedToSearch: "搜索失败。请稍后再试。",
|
||||
searchCancelled: "搜索已取消",
|
||||
openInExternalPlayer: "在外部播放器中打开",
|
||||
playWith: "使用此应用播放...",
|
||||
|
||||
// Login
|
||||
signIn: "登录",
|
||||
|
||||
Reference in New Issue
Block a user