continue watching

there are so many bugs
This commit is contained in:
James Hawkins 2021-08-06 14:39:54 +01:00
parent 32ed5e1340
commit ae0698ef80
5 changed files with 193 additions and 66 deletions

View File

@ -24,6 +24,7 @@
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.movieRow .left .seasonEpisodeSubtitle,
.movieRow .left .year { .movieRow .left .year {
color: var(--text-secondary); color: var(--text-secondary);
} }

View File

@ -20,14 +20,14 @@ export function MovieRow(props) {
return ( return (
<div className="movieRow" onClick={() => props.onClick && props.onClick()}> <div className="movieRow" onClick={() => props.onClick && props.onClick()}>
<div className="left"> <div className="left">
{props.title}&nbsp; {props.title} <span className="seasonEpisodeSubtitle">{props.place ? ` — Season ${props.place.season}: episode ${props.place.episode}` : ''}</span>&nbsp;
<span className="year">({props.year})</span> <span className="year">({props.year})</span>
</div> </div>
<div className="watch"> <div className="watch">
<p>Watch {props.type}</p> <p>Watch {props.type}</p>
<Arrow/> <Arrow/>
</div> </div>
<PercentageOverlay percentage={percentage} /> <PercentageOverlay percentage={props.percentage || percentage} />
</div> </div>
) )
} }

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { useCallback } from 'react'
import { useRouteMatch, useHistory } from 'react-router-dom' import { useRouteMatch, useHistory } from 'react-router-dom'
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Title } from '../components/Title' import { Title } from '../components/Title'
@ -14,6 +14,7 @@ export function MovieView(props) {
const baseRouteMatch = useRouteMatch('/:type/:source/:title/:slug'); const baseRouteMatch = useRouteMatch('/:type/:source/:title/:slug');
const showRouteMatch = useRouteMatch('/:type/:source/:title/:slug/season/:season/episode/:episode'); const showRouteMatch = useRouteMatch('/:type/:source/:title/:slug/season/:season/episode/:episode');
const history = useHistory(); const history = useHistory();
const { streamUrl, streamData, setStreamUrl } = useMovie(); const { streamUrl, streamData, setStreamUrl } = useMovie();
const [ seasonList, setSeasonList ] = React.useState([]); const [ seasonList, setSeasonList ] = React.useState([]);
const [ episodeLists, setEpisodeList ] = React.useState([]); const [ episodeLists, setEpisodeList ] = React.useState([]);
@ -23,6 +24,37 @@ export function MovieView(props) {
const season = showRouteMatch?.params.season || "1"; const season = showRouteMatch?.params.season || "1";
const episode = showRouteMatch?.params.episode || "1"; const episode = showRouteMatch?.params.episode || "1";
// eslint-disable-next-line react-hooks/exhaustive-deps
function setEpisode({ season, episode }) {
// getStream(title, slug, type, source, year);
// console.log(season, episode)
let tmpSeason = showRouteMatch.params.season;
let tmpEpisode = showRouteMatch.params.episode;
if (tmpSeason != season && tmpEpisode != episode)
history.replace(`${baseRouteMatch.url}/season/${season}/episode/${episode}`);
}
React.useEffect(() => {
// Cache streamData continue watching on home page
let movieCache = JSON.parse(localStorage.getItem("movie-cache") || "{}");
if (!movieCache[streamData.source]) movieCache[streamData.source] = {}
movieCache[streamData.source][streamData.slug] = {
cachedAt: Date.now()
}
localStorage.setItem("movie-cache", JSON.stringify(movieCache));
// Set season and episode list for GUI
if (streamData.type === "show") {
setSeasonList(streamData.seasons);
setSelectedSeason(streamData.seasons[0])
// TODO load from localstorage last watched
setEpisode({ episode: streamData.episodes[streamData.seasons[0]][0], season: streamData.seasons[0] })
setEpisodeList(streamData.episodes[streamData.seasons[0]]);
}
}, [streamData, setEpisode])
React.useEffect(() => { React.useEffect(() => {
setEpisodeList(streamData.episodes[selectedSeason]); setEpisodeList(streamData.episodes[selectedSeason]);
}, [selectedSeason, streamData.episodes]) }, [selectedSeason, streamData.episodes])
@ -39,14 +71,9 @@ export function MovieView(props) {
if (streamData.type === "show" && !showRouteMatch) history.replace(`${baseRouteMatch.url}/season/1/episode/1`); if (streamData.type === "show" && !showRouteMatch) history.replace(`${baseRouteMatch.url}/season/1/episode/1`);
}, [streamData, showRouteMatch, history, baseRouteMatch.url]); }, [streamData, showRouteMatch, history, baseRouteMatch.url]);
React.useEffect(() => {
if (streamData.type === "show" && !showRouteMatch) history.replace(`${baseRouteMatch.url}/season/1/episode/1`);
}, [streamData, showRouteMatch, history, baseRouteMatch.url]);
React.useEffect(() => { React.useEffect(() => {
if (streamData.type === "show" && showRouteMatch) setSelectedSeason(showRouteMatch.params.season.toString()); if (streamData.type === "show" && showRouteMatch) setSelectedSeason(showRouteMatch.params.season.toString());
// eslint-disable-next-line react-hooks/exhaustive-deps }, [showRouteMatch, streamData]);
}, []);
React.useEffect(() => { React.useEffect(() => {
let cancel = false; let cancel = false;
@ -71,19 +98,38 @@ export function MovieView(props) {
if (cancel) return; if (cancel) return;
console.error(e) console.error(e)
}) })
return () => { return () => {
cancel = true; cancel = true;
} }
}, [episode, streamData, setStreamUrl, season]); }, [episode, streamData, setStreamUrl, season]);
function setEpisode({ season, episode }) { React.useEffect(() => {
history.push(`${baseRouteMatch.url}/season/${season}/episode/${episode}`); // Cache streamData continue watching on home page
let movieCache = JSON.parse(localStorage.getItem("movie-cache") || "{}");
if(!movieCache[streamData.source]) movieCache[streamData.source] = {}
movieCache[streamData.source][streamData.slug] = {
cachedAt: Date.now()
} }
localStorage.setItem("movie-cache", JSON.stringify(movieCache));
// Set season and episode list for GUI
if (streamData.type === "show") {
setSeasonList(streamData.seasons);
setSelectedSeason(streamData.seasons[0])
// TODO load from localstorage last watched
setEpisode({ episode: streamData.episodes[streamData.seasons[0]][0], season: streamData.seasons[0] })
setEpisodeList(streamData.episodes[streamData.seasons[0]]);
}
}, [streamData, setEpisode])
const setProgress = (evt) => { const setProgress = (evt) => {
let ls = JSON.parse(localStorage.getItem("video-progress") || "{}") let ls = JSON.parse(localStorage.getItem("video-progress") || "{}")
// We're just checking lookmovie for now since there is only one scraper console.log(streamData);
if(!ls[streamData.source]) ls[streamData.source] = {} if(!ls[streamData.source]) ls[streamData.source] = {}
if(!ls[streamData.source][streamData.type]) ls[streamData.source][streamData.type] = {} if(!ls[streamData.source][streamData.type]) ls[streamData.source][streamData.type] = {}
if(!ls[streamData.source][streamData.type][streamData.slug]) { if(!ls[streamData.source][streamData.type][streamData.slug]) {
@ -91,17 +137,18 @@ export function MovieView(props) {
} }
// Store real data // Store real data
let key = streamData.type === "show" ? `${season}-${episode}` : "full" let key = streamData.type === "show" ? `${season}-${episode.episode}` : "full"
ls[streamData.source][streamData.type][streamData.slug][key] = { ls[streamData.source][streamData.type][streamData.slug][key] = {
currentlyAt: Math.floor(evt.currentTarget.currentTime), currentlyAt: Math.floor(evt.currentTarget.currentTime),
totalDuration: Math.floor(evt.currentTarget.duration), totalDuration: Math.floor(evt.currentTarget.duration),
updatedAt: Date.now() updatedAt: Date.now(),
meta: streamData
} }
if(streamData.type === "show") { if(streamData.type === "show") {
ls[streamData.source][streamData.type][streamData.slug][key].show = { ls[streamData.source][streamData.type][streamData.slug][key].show = {
season, season,
episode: episode episode: episode.episode
} }
} }

View File

@ -8,11 +8,30 @@
box-sizing: border-box; box-sizing: border-box;
} }
.cardView > div { .cardView nav {
width: 100%;
max-width: 624px;
}
.cardView nav a {
padding: 8px 16px;
margin-right: 10px;
border-radius: 4px;
color: var(--text);
}
.cardView nav a:not(.selected-link) {
cursor: pointer;
}
.cardView nav a.selected-link {
background: var(--button);
color: var(--button-text);
font-weight: bold;
}
.cardView > * {
margin-top: 2rem; margin-top: 2rem;
} }
.cardView > div:first-child { .cardView > *:first-child {
margin-top: 38px; margin-top: 38px;
} }

View File

@ -30,6 +30,8 @@ export function SearchView() {
const [failed, setFailed] = React.useState(false); const [failed, setFailed] = React.useState(false);
const [showingOptions, setShowingOptions] = React.useState(false); const [showingOptions, setShowingOptions] = React.useState(false);
const [errorStatus, setErrorStatus] = React.useState(false); const [errorStatus, setErrorStatus] = React.useState(false);
const [page, setPage] = React.useState('search');
const [continueWatching, setContinueWatching] = React.useState([])
const fail = (str) => { const fail = (str) => {
setProgress(maxSteps); setProgress(maxSteps);
@ -37,7 +39,7 @@ export function SearchView() {
setFailed(true) setFailed(true)
} }
async function getStream(title, slug, type, source) { async function getStream(title, slug, type, source, year) {
setStreamUrl(""); setStreamUrl("");
try { try {
@ -71,7 +73,8 @@ export function SearchView() {
seasons, seasons,
episodes, episodes,
slug, slug,
source source,
year
}) })
setText(`Streaming...`) setText(`Streaming...`)
navigate("movie") navigate("movie")
@ -100,9 +103,9 @@ export function SearchView() {
return; return;
} }
const { title, slug, type, source } = options[0]; const { title, slug, type, source, year } = options[0];
history.push(`${routeMatch.url}/${source}/${title}/${slug}`); history.push(`${routeMatch.url}/${source}/${title}/${slug}`);
getStream(title, slug, type, source); getStream(title, slug, type, source, year);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
fail(`Failed to watch ${contentType}`) fail(`Failed to watch ${contentType}`)
@ -128,7 +131,38 @@ export function SearchView() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
if (!type || (type !== 'movie' && type !== 'show')) return <Redirect to="/movie" /> React.useEffect(() => {
const progressData = JSON.parse(localStorage.getItem('video-progress') || "{}")
let newContinueWatching = []
Object.keys(progressData).forEach(source => {
const all = [
...Object.entries(progressData[source]?.show ?? {}),
...Object.entries(progressData[source]?.movie ?? {})
]
for (const [slug, data] of all) {
for (let subselection of Object.values(data)) {
let entry = {
slug,
data: subselection,
type: subselection.show ? 'show' : 'movie',
percentageDone: Math.floor((subselection.currentlyAt / subselection.totalDuration) * 100),
source
}
if (entry.percentageDone < 90) {
newContinueWatching.push(entry)
}
}
}
newContinueWatching = newContinueWatching.sort((a, b) => {
return b.data.updatedAt - a.data.updatedAt
})
setContinueWatching(newContinueWatching)
})
}, []);
if (!type || (type !== 'movie' && type !== 'show')) {
return <Redirect to="/movie" />
}
return ( return (
<div className="cardView"> <div className="cardView">
@ -136,6 +170,17 @@ export function SearchView() {
<title>{type === 'movie' ? 'Movies' : 'TV Shows'} | movie-web</title> <title>{type === 'movie' ? 'Movies' : 'TV Shows'} | movie-web</title>
</Helmet> </Helmet>
{/* Nav */}
<nav>
<a className={page === 'search' ? 'selected-link' : ''} onClick={() => setPage('search')} href>Search</a>
{continueWatching.length > 0 ?
<a className={page === 'watching' ? 'selected-link' : ''} onClick={() => setPage('watching')} href>Continue watching</a>
: ''}
</nav>
{/* Search */}
{page === 'search' ?
<React.Fragment>
<Card> <Card>
<DiscordBanner /> <DiscordBanner />
{errorStatus ? <ErrorBanner>{errorStatus}</ErrorBanner> : ''} {errorStatus ? <ErrorBanner>{errorStatus}</ErrorBanner> : ''}
@ -170,13 +215,28 @@ export function SearchView() {
<MovieRow key={i} title={v.title} slug={v.slug} type={v.type} year={v.year} source={v.source} onClick={() => { <MovieRow key={i} title={v.title} slug={v.slug} type={v.type} year={v.year} source={v.source} onClick={() => {
history.push(`${routeMatch.url}/${v.source}/${v.title}/${v.slug}`); history.push(`${routeMatch.url}/${v.source}/${v.title}/${v.slug}`);
setShowingOptions(false) setShowingOptions(false)
getStream(v.title, v.slug, v.type, v.source) getStream(v.title, v.slug, v.type, v.source, v.year)
}} /> }} />
))} ))}
</div> </div>
)) ))
} }
</Card> </Card>
</React.Fragment> : <React.Fragment />}
{/* Continue watching */}
{continueWatching.length > 0 && page === 'watching' ? <Card>
<Title>Continue watching</Title>
{console.log(continueWatching)}
{continueWatching?.map((v, i) => (
<MovieRow key={i} title={v.data.meta.title} slug={v.data.meta.slug} type={v.type} year={v.data.meta.year} source={v.source} place={v.data.show} percentage={v.percentageDone} onClick={() => {
history.push(`${routeMatch.url}/${v.source}/${v.data.meta.title}/${v.slug}`)
setShowingOptions(false)
getStream(v.data.meta.title, v.data.meta.slug, v.type, v.source, v.year)
}} />
))}
</Card> : <React.Fragment></React.Fragment>}
<div className="topRightCredits"> <div className="topRightCredits">
<a href="https://github.com/JamesHawkinss/movie-web" target="_blank" rel="noreferrer">Check it out on GitHub <Arrow /></a> <a href="https://github.com/JamesHawkinss/movie-web" target="_blank" rel="noreferrer">Check it out on GitHub <Arrow /></a>
<br /> <br />