barebones anime player

This commit is contained in:
Aria Moradi 2021-05-27 18:37:45 +04:30
parent 5c7123a997
commit bd02edf0b1
7 changed files with 139 additions and 25 deletions

View File

@ -21,7 +21,6 @@ import suwayomi.anime.impl.extension.Extension.installExtension
import suwayomi.anime.impl.extension.Extension.uninstallExtension import suwayomi.anime.impl.extension.Extension.uninstallExtension
import suwayomi.anime.impl.extension.Extension.updateExtension import suwayomi.anime.impl.extension.Extension.updateExtension
import suwayomi.anime.impl.extension.ExtensionsList.getExtensionList import suwayomi.anime.impl.extension.ExtensionsList.getExtensionList
import suwayomi.server.JavalinSetup
import suwayomi.server.JavalinSetup.future import suwayomi.server.JavalinSetup.future
object AnimeAPI { object AnimeAPI {
@ -40,7 +39,7 @@ object AnimeAPI {
val pkgName = ctx.pathParam("pkgName") val pkgName = ctx.pathParam("pkgName")
ctx.json( ctx.json(
JavalinSetup.future { future {
installExtension(pkgName) installExtension(pkgName)
} }
) )
@ -51,7 +50,7 @@ object AnimeAPI {
val pkgName = ctx.pathParam("pkgName") val pkgName = ctx.pathParam("pkgName")
ctx.json( ctx.json(
JavalinSetup.future { future {
updateExtension(pkgName) updateExtension(pkgName)
} }
) )
@ -70,7 +69,7 @@ object AnimeAPI {
val apkName = ctx.pathParam("apkName") val apkName = ctx.pathParam("apkName")
ctx.result( ctx.result(
JavalinSetup.future { getExtensionIcon(apkName) } future { getExtensionIcon(apkName) }
.thenApply { .thenApply {
ctx.header("content-type", it.second) ctx.header("content-type", it.second)
it.first it.first
@ -94,7 +93,7 @@ object AnimeAPI {
val sourceId = ctx.pathParam("sourceId").toLong() val sourceId = ctx.pathParam("sourceId").toLong()
val pageNum = ctx.pathParam("pageNum").toInt() val pageNum = ctx.pathParam("pageNum").toInt()
ctx.json( ctx.json(
JavalinSetup.future { future {
getAnimeList(sourceId, pageNum, popular = true) getAnimeList(sourceId, pageNum, popular = true)
} }
) )
@ -105,7 +104,7 @@ object AnimeAPI {
val sourceId = ctx.pathParam("sourceId").toLong() val sourceId = ctx.pathParam("sourceId").toLong()
val pageNum = ctx.pathParam("pageNum").toInt() val pageNum = ctx.pathParam("pageNum").toInt()
ctx.json( ctx.json(
JavalinSetup.future { future {
getAnimeList(sourceId, pageNum, popular = false) getAnimeList(sourceId, pageNum, popular = false)
} }
) )
@ -117,7 +116,7 @@ object AnimeAPI {
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean() val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
ctx.json( ctx.json(
JavalinSetup.future { future {
getAnime(animeId, onlineFetch) getAnime(animeId, onlineFetch)
} }
) )
@ -128,7 +127,7 @@ object AnimeAPI {
val animeId = ctx.pathParam("animeId").toInt() val animeId = ctx.pathParam("animeId").toInt()
ctx.result( ctx.result(
JavalinSetup.future { getAnimeThumbnail(animeId) } future { getAnimeThumbnail(animeId) }
.thenApply { .thenApply {
ctx.header("content-type", it.second) ctx.header("content-type", it.second)
it.first it.first
@ -164,14 +163,14 @@ object AnimeAPI {
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean() val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean()
ctx.json(JavalinSetup.future { getEpisodeList(animeId, onlineFetch) }) ctx.json(future { getEpisodeList(animeId, onlineFetch) })
} }
// used to display a episode, get a episode in order to show it's <Quality pending> // used to display a episode, get a episode in order to show it's <Quality pending>
app.get("/api/v1/anime/anime/:animeId/episode/:episodeIndex") { ctx -> app.get("/api/v1/anime/anime/:animeId/episode/:episodeIndex") { ctx ->
val episodeIndex = ctx.pathParam("episodeIndex").toInt() val episodeIndex = ctx.pathParam("episodeIndex").toInt()
val animeId = ctx.pathParam("animeId").toInt() val animeId = ctx.pathParam("animeId").toInt()
ctx.json(JavalinSetup.future { getEpisode(episodeIndex, animeId) }) ctx.json(future { getEpisode(episodeIndex, animeId) })
} }
// used to modify a episode's parameters // used to modify a episode's parameters

View File

@ -8,6 +8,7 @@ package suwayomi.anime.impl
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.SAnime import eu.kanade.tachiyomi.source.model.SAnime
import eu.kanade.tachiyomi.source.model.SEpisode
import org.jetbrains.exposed.sql.SortOrder.DESC import org.jetbrains.exposed.sql.SortOrder.DESC
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.deleteWhere
@ -18,6 +19,7 @@ import org.jetbrains.exposed.sql.update
import suwayomi.anime.impl.Anime.getAnime import suwayomi.anime.impl.Anime.getAnime
import suwayomi.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource import suwayomi.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
import suwayomi.anime.model.dataclass.EpisodeDataClass import suwayomi.anime.model.dataclass.EpisodeDataClass
import suwayomi.anime.model.table.AnimeTable
import suwayomi.anime.model.table.EpisodeTable import suwayomi.anime.model.table.EpisodeTable
import suwayomi.anime.model.table.toDataClass import suwayomi.anime.model.table.toDataClass
import suwayomi.tachidesk.impl.util.lang.awaitSingle import suwayomi.tachidesk.impl.util.lang.awaitSingle
@ -59,7 +61,7 @@ object Episode {
val episodeEntry = EpisodeTable.select { EpisodeTable.url eq fetchedEpisode.url }.firstOrNull() val episodeEntry = EpisodeTable.select { EpisodeTable.url eq fetchedEpisode.url }.firstOrNull()
if (episodeEntry == null) { if (episodeEntry == null) {
EpisodeTable.insert { EpisodeTable.insert {
it[url] = source. it[url] = fetchedEpisode.url
it[name] = fetchedEpisode.name it[name] = fetchedEpisode.name
it[date_upload] = fetchedEpisode.date_upload it[date_upload] = fetchedEpisode.date_upload
it[episode_number] = fetchedEpisode.episode_number it[episode_number] = fetchedEpisode.episode_number
@ -128,7 +130,32 @@ object Episode {
/** used to display a episode, get a episode in order to show it's video */ /** used to display a episode, get a episode in order to show it's video */
suspend fun getEpisode(episodeIndex: Int, animeId: Int): EpisodeDataClass { suspend fun getEpisode(episodeIndex: Int, animeId: Int): EpisodeDataClass {
return getEpisodeList(animeId, true).first { it.index == episodeIndex } val episode = getEpisodeList(animeId, false)
.first { it.index == episodeIndex }
val animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() }
val source = getAnimeHttpSource(animeEntry[AnimeTable.sourceReference])
val fetchedLinkUrl = source.fetchEpisodeLink(
SEpisode.create().also {
it.url = episode.url
it.name = episode.name
}
).awaitSingle()
return EpisodeDataClass(
episode.url,
episode.name,
episode.uploadDate,
episode.episodeNumber,
episode.scanlator,
animeId,
episode.read,
episode.bookmarked,
episode.lastPageRead,
episode.index,
episode.episodeCount,
fetchedLinkUrl
)
} }
// /** used to display a episode, get a episode in order to show it's pages */ // /** used to display a episode, get a episode in order to show it's pages */

View File

@ -27,9 +27,9 @@ data class EpisodeDataClass(
/** this chapter's index, starts with 1 */ /** this chapter's index, starts with 1 */
val index: Int, val index: Int,
/** total chapter count, used to calculate if there's a next and prev chapter */ /** total episode count, used to calculate if there's a next and prev episode */
val chapterCount: Int? = null, val episodeCount: Int? = null,
/** used to construct pages in the front-end */ /** used to construct pages in the front-end */
val pageCount: Int? = null, val linkUrl: String? = null,
) )

View File

@ -9,6 +9,8 @@ package suwayomi.anime.model.table
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.anime.model.dataclass.EpisodeDataClass import suwayomi.anime.model.dataclass.EpisodeDataClass
object EpisodeTable : IntIdTable() { object EpisodeTable : IntIdTable() {
@ -40,4 +42,5 @@ fun EpisodeTable.toDataClass(episodeEntry: ResultRow) =
episodeEntry[isBookmarked], episodeEntry[isBookmarked],
episodeEntry[lastPageRead], episodeEntry[lastPageRead],
episodeEntry[episodeIndex], episodeEntry[episodeIndex],
transaction { EpisodeTable.select { anime eq episodeEntry[anime] }.count().toInt() }
) )

View File

@ -32,6 +32,7 @@ import MangaExtensions from 'screens/manga/MangaExtensions';
import SourceMangas from 'screens/manga/SourceMangas'; import SourceMangas from 'screens/manga/SourceMangas';
import SourceAnimes from 'screens/anime/SourceAnimes'; import SourceAnimes from 'screens/anime/SourceAnimes';
import Reader from 'screens/manga/Reader'; import Reader from 'screens/manga/Reader';
import Player from 'screens/anime/Player';
import AnimeExtensions from 'screens/anime/AnimeExtensions'; import AnimeExtensions from 'screens/anime/AnimeExtensions';
export default function App() { export default function App() {
@ -133,13 +134,6 @@ export default function App() {
<Route path="/library"> <Route path="/library">
<Library /> <Library />
</Route> </Route>
<Route
path="/manga/:mangaId/chapter/:chapterIndex"
// passing a key re-mounts the reader when changing chapters
render={
(props:any) => <Reader key={props.match.params.chapterIndex} />
}
/>
{/* Anime Routes */} {/* Anime Routes */}
<Route path="/anime/extensions"> <Route path="/anime/extensions">
@ -154,11 +148,21 @@ export default function App() {
<Route path="/anime/sources"> <Route path="/anime/sources">
<AnimeSources /> <AnimeSources />
</Route> </Route>
<Route path="/anime/:animeId/episode/:episodeIndex">
<Player />
</Route>
<Route path="/anime/:id"> <Route path="/anime/:id">
<Anime /> <Anime />
</Route> </Route>
</Switch> </Switch>
</Container> </Container>
<Switch>
<Route
path="/manga/:mangaId/chapter/:chapterIndex"
// passing a key re-mounts the reader when changing chapters
render={(props:any) => <Reader key={props.match.params.chapterIndex} />}
/>
</Switch>
</NavbarContext.Provider> </NavbarContext.Provider>
</ThemeProvider> </ThemeProvider>
</Router> </Router>

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import CircularProgress from '@material-ui/core/CircularProgress';
import { makeStyles } from '@material-ui/core/styles';
import React, { useContext, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import NavbarContext from 'context/NavbarContext';
import client from 'util/client';
const useStyles = makeStyles({
root: {
width: 'calc(100vw - 10px)',
height: 'calc(100vh - 64px)',
},
loading: {
margin: '50px auto',
},
video: {
maxWidth: '100%',
maxHeight: '100%',
},
});
const initialEpisode = () => ({ linkUrl: '', index: -1, episodeCount: 0 });
export default function Player() {
const classes = useStyles();
const { episodeIndex, animeId } = useParams<{ episodeIndex: string, animeId: string }>();
const [episode, setEpisode] = useState<IEpisode | IPartialEpisode>(initialEpisode());
const [episodeLink, setEpisodeLink] = useState<string>();
const { setTitle } = useContext(NavbarContext);
useEffect(() => {
setTitle('Reader');
client.get(`/api/v1/anime/anime/${animeId}/`)
.then((response) => response.data)
.then((data: IManga) => {
setTitle(data.title);
});
}, [episodeIndex]);
useEffect(() => {
setEpisode(initialEpisode);
client.get(`/api/v1/anime/anime/${animeId}/episode/${episodeIndex}`)
.then((response) => response.data)
.then((data:IEpisode) => {
setEpisode(data);
setEpisodeLink(data.linkUrl);
});
}, [episodeIndex]);
// return spinner while chpater data is loading
if (episode.linkUrl === '') {
return (
<div className={classes.loading}>
<CircularProgress thickness={5} />
</div>
);
}
return (
<div className={classes.root}>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video className={classes.video} controls>
<source src={episodeLink} />
</video>
</div>
);
}

View File

@ -55,7 +55,6 @@ interface IManga {
} }
interface IChapter { interface IChapter {
id: number
url: string url: string
name: string name: string
uploadDate: number uploadDate: number
@ -71,7 +70,6 @@ interface IChapter {
} }
interface IEpisode { interface IEpisode {
id: number
url: string url: string
name: string name: string
uploadDate: number uploadDate: number
@ -83,7 +81,7 @@ interface IEpisode {
lastPageRead: number lastPageRead: number
index: number index: number
episodeCount: number episodeCount: number
pageCount: number linkUrl: string
} }
interface IPartialChpter { interface IPartialChpter {
@ -92,6 +90,12 @@ interface IPartialChpter {
chapterCount: number chapterCount: number
} }
interface IPartialEpisode {
linkUrl: string
index: number
episodeCount: number
}
interface ICategory { interface ICategory {
id: number id: number
order: number order: number