frontend/app/Entry/MediaPreview.tsx (177 lines of code) (raw):
import React, {useEffect, useState} from "react";
import {MimeType, ProxyLocation, ProxyLocationsResponse, ProxyType, StylesMap} from "../types";
import axios from "axios";
import {formatError} from "../common/ErrorViewComponent";
import {Button, CircularProgress, Grid, makeStyles, Typography} from "@material-ui/core";
import {baseStyles} from "../BaseStyles";
import EntryPreviewSwitcher from "./EntryPreviewSwitcher";
import clsx from "clsx";
import MediaPlayer from "./MediaPlayer";
import EntryThumbnail from "./EntryThumbnail";
import ReconnectDialog from "./ReconnectDialog";
interface MediaPreviewProps {
onError?: (errorString:string)=>void;
triggeredProxyGeneration?: ()=>void;
itemId: string;
itemName: string;
fileExtension?: string;
mimeType: MimeType;
className?: string;
relinkedCb?: ()=>void;
}
const useStyles = makeStyles((theme)=>Object.assign({
partialDivider: {
width: "70%",
},
errorText: {
color: theme.palette.error.dark,
fontWeight: "bold"
},
processingText: {
color: theme.palette.success,
fontStyle: "italic"
},
thumbNote: {
marginTop: 0,
marginBottom: 0,
fontStyle: "italic"
},
centeredMedia: {
marginLeft: "auto",
marginRight: "auto",
width: "100%",
textAlign: "center",
}
} as StylesMap, baseStyles));
const MediaPreview:React.FC<MediaPreviewProps> = (props) => {
const [proxyLocations, setProxyLocations] = useState<ProxyLocation[]>([]);
const [loading, setLoading] = useState(true);
const [selectedPreview, setSelectedPreview] = useState<ProxyType>("VIDEO");
const [processMessage, setProcessMessage] = useState<string|undefined>(undefined);
const [autoPlay, setAutoPlay] = useState(true);
const [showingCreate, setShowingCreate] = useState(false);
const [potentialProxies, setPotentialProxies] = useState<undefined|ProxyLocation[]>(undefined);
const [showingReconnect, setShowingReconnect] = useState(false);
const classes = useStyles();
useEffect(()=>{
const loadData = async ()=> {
try {
const response = await axios.get<ProxyLocationsResponse>(`/api/proxy/${props.itemId}`);
setProxyLocations(response.data.entries);
setLoading(false);
} catch(err) {
setLoading(false);
if(props.onError) props.onError(formatError(err, false));
}
}
loadData();
}, [props.itemId]);
const bestAvailablePreview = (proxyTypes:ProxyType[])=>{
if(proxyTypes.includes("VIDEO")) return "VIDEO";
if(proxyTypes.includes("AUDIO")) return "AUDIO";
if(proxyTypes.includes("POSTER")) return "POSTER";
if(proxyTypes.includes("THUMBNAIL")) return "THUMBNAIL";
return "THUMBNAIL";
}
useEffect(()=>{
const proxyTypes = proxyLocations.map(loc=>loc.proxyType);
setSelectedPreview(bestAvailablePreview(proxyTypes))
}, [proxyLocations]);
const newTypeSelected = (newType:ProxyType)=> {
const proxyTypes = proxyLocations.map(loc=>loc.proxyType);
setShowingCreate(!proxyTypes.includes(newType));
setSelectedPreview(newType)
}
const initiateCreateProxy = async ()=>{
try {
const result = await axios.post("/api/proxy/generate/" + props.itemId + "/" + selectedPreview.toLowerCase());
const msg = result.data.entry==="disabled" ? "Proxy generation disabled for this storage" : "Proxy generation started";
setProcessMessage(msg);
if(props.triggeredProxyGeneration && result.data.entry!=="disabled") props.triggeredProxyGeneration();
} catch(err) {
console.log(err);
setProcessMessage("Proxy generation failed, see console log");
if(props.onError) props.onError(formatError(err, false));
}
}
const initiateRelinkSearch = async ()=>{
try {
setProcessMessage("Searching...")
const result = await axios.get<ProxyLocationsResponse>("/api/proxy/searchForFile?id=" + encodeURIComponent(props.itemId));
const autoLinked = result.data.entryCount==1
setProcessMessage(`Found ${result.data.entryCount} potential proxies${autoLinked ? ", automatically linked" : "."}`);
if(result.data.entryCount>1) {
setPotentialProxies(result.data.entries);
if(props.relinkedCb) props.relinkedCb();
}
} catch(err) {
console.log(err);
setProcessMessage(formatError(err, true));
if(props.onError) props.onError(formatError(err, false));
}
}
const handleProxyNotFound = (proxyType:ProxyType) => {
setShowingCreate(true);
}
const handleReconnectClosed = (didSave:boolean) => {
if(didSave) {
setProcessMessage("Proxy associated, reload to view");
if(props.relinkedCb) props.relinkedCb();
} else {
setProcessMessage("");
}
setShowingReconnect(false);
setPotentialProxies(undefined);
}
if(loading) {
return <div className={classes.centered}>
<CircularProgress/>
</div>
}
return <div className={props.className}>
{
showingCreate ? <>
<EntryThumbnail mimeType={props.mimeType} fileExtension={props.fileExtension} entryId={props.itemId}/>
<p className={clsx(classes.thumbnote, classes.centered)}>There is no {selectedPreview} proxy available</p>
<Grid direction="row" container style={{marginTop: "0.4em"}} justify="space-around">
<Grid item>
<Button variant="outlined"
onClick={initiateCreateProxy}>
Create
</Button>
</Grid>
<Grid item>
{
potentialProxies ? <Button variant="outlined"
onClick={()=>setShowingReconnect(true)}>
Reconnect
</Button>:
<Button variant="outlined"
onClick={initiateRelinkSearch}>
Re-check
</Button>
}
</Grid>
</Grid>
</> : <MediaPlayer entryId={props.itemId}
onError={props.onError}
mimeType={props.mimeType}
playableType={selectedPreview}
onProxyNotFound={handleProxyNotFound}
autoPlay={autoPlay}/>
}
<hr className={classes.partialDivider}/>
<EntryPreviewSwitcher availableTypes={proxyLocations.map(loc=>loc.proxyType).join(",")} typeSelected={newTypeSelected}/>
{ processMessage ? <Typography className={clsx(classes.processingText, classes.centered)}>{processMessage}</Typography> : ""}
<hr className={classes.partialDivider}/>
{
showingReconnect && potentialProxies ? <ReconnectDialog potentialProxies={potentialProxies}
itemName={props.itemName}
itemId={props.itemId}
onDialogClose={handleReconnectClosed}
onError={props.onError}
/> : undefined
}
</div>
}
export default MediaPreview;