diff --git a/src/components/MovieRow.css b/src/components/MovieRow.css
index a32463b5..4ef12f41 100644
--- a/src/components/MovieRow.css
+++ b/src/components/MovieRow.css
@@ -24,6 +24,7 @@
margin-right: 0.5rem;
}
+.movieRow .left .seasonEpisodeSubtitle,
.movieRow .left .year {
color: var(--text-secondary);
}
diff --git a/src/components/MovieRow.js b/src/components/MovieRow.js
index d8ae3162..d15d35ef 100644
--- a/src/components/MovieRow.js
+++ b/src/components/MovieRow.js
@@ -20,14 +20,14 @@ export function MovieRow(props) {
return (
props.onClick && props.onClick()}>
- {props.title}
+ {props.title} {props.place ? ` — Season ${props.place.season}: episode ${props.place.episode}` : ''}
({props.year})
-
+
)
}
diff --git a/src/views/Movie.js b/src/views/Movie.js
index cec810e9..71c72f0c 100644
--- a/src/views/Movie.js
+++ b/src/views/Movie.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useCallback } from 'react'
import { useRouteMatch, useHistory } from 'react-router-dom'
import { Helmet } from 'react-helmet';
import { Title } from '../components/Title'
@@ -14,15 +14,47 @@ export function MovieView(props) {
const baseRouteMatch = useRouteMatch('/:type/:source/:title/:slug');
const showRouteMatch = useRouteMatch('/:type/:source/:title/:slug/season/:season/episode/:episode');
const history = useHistory();
+
const { streamUrl, streamData, setStreamUrl } = useMovie();
- const [seasonList, setSeasonList] = React.useState([]);
- const [episodeLists, setEpisodeList] = React.useState([]);
- const [loading, setLoading] = React.useState(false);
+ const [ seasonList, setSeasonList ] = React.useState([]);
+ const [ episodeLists, setEpisodeList ] = React.useState([]);
+ const [ loading, setLoading ] = React.useState(false);
const [ selectedSeason, setSelectedSeason ] = React.useState("1");
const season = showRouteMatch?.params.season || "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(() => {
setEpisodeList(streamData.episodes[selectedSeason]);
}, [selectedSeason, streamData.episodes])
@@ -39,14 +71,9 @@ export function MovieView(props) {
if (streamData.type === "show" && !showRouteMatch) history.replace(`${baseRouteMatch.url}/season/1/episode/1`);
}, [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(() => {
if (streamData.type === "show" && showRouteMatch) setSelectedSeason(showRouteMatch.params.season.toString());
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ }, [showRouteMatch, streamData]);
React.useEffect(() => {
let cancel = false;
@@ -71,19 +98,38 @@ export function MovieView(props) {
if (cancel) return;
console.error(e)
})
+
return () => {
cancel = true;
}
}, [episode, streamData, setStreamUrl, season]);
- function setEpisode({ season, episode }) {
- history.push(`${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])
const setProgress = (evt) => {
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][streamData.type]) ls[streamData.source][streamData.type] = {}
if(!ls[streamData.source][streamData.type][streamData.slug]) {
@@ -91,17 +137,18 @@ export function MovieView(props) {
}
// 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] = {
currentlyAt: Math.floor(evt.currentTarget.currentTime),
totalDuration: Math.floor(evt.currentTarget.duration),
- updatedAt: Date.now()
+ updatedAt: Date.now(),
+ meta: streamData
}
if(streamData.type === "show") {
ls[streamData.source][streamData.type][streamData.slug][key].show = {
season,
- episode: episode
+ episode: episode.episode
}
}
diff --git a/src/views/Search.css b/src/views/Search.css
index 6795e5a1..903b854c 100644
--- a/src/views/Search.css
+++ b/src/views/Search.css
@@ -8,11 +8,30 @@
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;
}
-.cardView > div:first-child {
+.cardView > *:first-child {
margin-top: 38px;
}
diff --git a/src/views/Search.js b/src/views/Search.js
index 836c9088..715534d0 100644
--- a/src/views/Search.js
+++ b/src/views/Search.js
@@ -30,6 +30,8 @@ export function SearchView() {
const [failed, setFailed] = React.useState(false);
const [showingOptions, setShowingOptions] = React.useState(false);
const [errorStatus, setErrorStatus] = React.useState(false);
+ const [page, setPage] = React.useState('search');
+ const [continueWatching, setContinueWatching] = React.useState([])
const fail = (str) => {
setProgress(maxSteps);
@@ -37,7 +39,7 @@ export function SearchView() {
setFailed(true)
}
- async function getStream(title, slug, type, source) {
+ async function getStream(title, slug, type, source, year) {
setStreamUrl("");
try {
@@ -71,7 +73,8 @@ export function SearchView() {
seasons,
episodes,
slug,
- source
+ source,
+ year
})
setText(`Streaming...`)
navigate("movie")
@@ -100,9 +103,9 @@ export function SearchView() {
return;
}
- const { title, slug, type, source } = options[0];
+ const { title, slug, type, source, year } = options[0];
history.push(`${routeMatch.url}/${source}/${title}/${slug}`);
- getStream(title, slug, type, source);
+ getStream(title, slug, type, source, year);
} catch (err) {
console.error(err);
fail(`Failed to watch ${contentType}`)
@@ -128,7 +131,38 @@ export function SearchView() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- if (!type || (type !== 'movie' && type !== 'show')) return
+ 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
+ }
return (
@@ -136,47 +170,73 @@ export function SearchView() {
{type === 'movie' ? 'Movies' : 'TV Shows'} | movie-web
-
-
- {errorStatus ? {errorStatus} : ''}
-
- What do you wanna watch?
-
- history.push(`/${type}`)}
- choices={[
- { label: "Movie", value: "movie" },
- { label: "TV Show", value: "show" }
- ]}
- noWrap={true}
- selected={type}
- />
- searchMovie(str, type)} />
-
+ {/* Nav */}
+
+
+ {/* Search */}
+ {page === 'search' ?
+
+
+
+ {errorStatus ? {errorStatus} : ''}
+
+ What do you wanna watch?
+
+ history.push(`/${type}`)}
+ choices={[
+ { label: "Movie", value: "movie" },
+ { label: "TV Show", value: "show" }
+ ]}
+ noWrap={true}
+ selected={type}
+ />
+ searchMovie(str, type)} />
+
+
+
+
+ Whoops, there are a few {type}s like that
+
+ {Object.entries(options.reduce((a, v) => {
+ if (!a[v.source]) a[v.source] = []
+ a[v.source].push(v)
+ return a;
+ }, {})).map(v => (
+
+
{v[0]}
+ {v[1].map((v, i) => (
+
{
+ history.push(`${routeMatch.url}/${v.source}/${v.title}/${v.slug}`);
+ setShowingOptions(false)
+ getStream(v.title, v.slug, v.type, v.source, v.year)
+ }} />
+ ))}
+
+ ))
+ }
+
+ :
}
+
+ {/* Continue watching */}
+ {continueWatching.length > 0 && page === 'watching' ?
+ Continue watching
+ {console.log(continueWatching)}
+ {continueWatching?.map((v, i) => (
+ {
+ 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)
+ }} />
+ ))}
+ :
}
-
-
- Whoops, there are a few {type}s like that
-
- { Object.entries(options.reduce((a, v) => {
- if (!a[v.source]) a[v.source] = []
- a[v.source].push(v)
- return a;
- }, {})).map(v => (
-
-
{v[0]}
- {v[1].map((v, i) => (
-
{
- history.push(`${routeMatch.url}/${v.source}/${v.title}/${v.slug}`);
- setShowingOptions(false)
- getStream(v.title, v.slug, v.type, v.source)
- }} />
- ))}
-
- ))
- }
-