mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2024-12-26 00:31:49 +01:00
barebones anime player
This commit is contained in:
parent
5c7123a997
commit
bd02edf0b1
@ -21,7 +21,6 @@ import suwayomi.anime.impl.extension.Extension.installExtension
|
||||
import suwayomi.anime.impl.extension.Extension.uninstallExtension
|
||||
import suwayomi.anime.impl.extension.Extension.updateExtension
|
||||
import suwayomi.anime.impl.extension.ExtensionsList.getExtensionList
|
||||
import suwayomi.server.JavalinSetup
|
||||
import suwayomi.server.JavalinSetup.future
|
||||
|
||||
object AnimeAPI {
|
||||
@ -40,7 +39,7 @@ object AnimeAPI {
|
||||
val pkgName = ctx.pathParam("pkgName")
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
installExtension(pkgName)
|
||||
}
|
||||
)
|
||||
@ -51,7 +50,7 @@ object AnimeAPI {
|
||||
val pkgName = ctx.pathParam("pkgName")
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
updateExtension(pkgName)
|
||||
}
|
||||
)
|
||||
@ -70,7 +69,7 @@ object AnimeAPI {
|
||||
val apkName = ctx.pathParam("apkName")
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { getExtensionIcon(apkName) }
|
||||
future { getExtensionIcon(apkName) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
@ -94,7 +93,7 @@ object AnimeAPI {
|
||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getAnimeList(sourceId, pageNum, popular = true)
|
||||
}
|
||||
)
|
||||
@ -105,7 +104,7 @@ object AnimeAPI {
|
||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getAnimeList(sourceId, pageNum, popular = false)
|
||||
}
|
||||
)
|
||||
@ -117,7 +116,7 @@ object AnimeAPI {
|
||||
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getAnime(animeId, onlineFetch)
|
||||
}
|
||||
)
|
||||
@ -128,7 +127,7 @@ object AnimeAPI {
|
||||
val animeId = ctx.pathParam("animeId").toInt()
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { getAnimeThumbnail(animeId) }
|
||||
future { getAnimeThumbnail(animeId) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
@ -164,14 +163,14 @@ object AnimeAPI {
|
||||
|
||||
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>
|
||||
app.get("/api/v1/anime/anime/:animeId/episode/:episodeIndex") { ctx ->
|
||||
val episodeIndex = ctx.pathParam("episodeIndex").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
|
||||
|
@ -8,6 +8,7 @@ package suwayomi.anime.impl
|
||||
* 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.SEpisode
|
||||
import org.jetbrains.exposed.sql.SortOrder.DESC
|
||||
import org.jetbrains.exposed.sql.and
|
||||
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.util.GetAnimeHttpSource.getAnimeHttpSource
|
||||
import suwayomi.anime.model.dataclass.EpisodeDataClass
|
||||
import suwayomi.anime.model.table.AnimeTable
|
||||
import suwayomi.anime.model.table.EpisodeTable
|
||||
import suwayomi.anime.model.table.toDataClass
|
||||
import suwayomi.tachidesk.impl.util.lang.awaitSingle
|
||||
@ -59,7 +61,7 @@ object Episode {
|
||||
val episodeEntry = EpisodeTable.select { EpisodeTable.url eq fetchedEpisode.url }.firstOrNull()
|
||||
if (episodeEntry == null) {
|
||||
EpisodeTable.insert {
|
||||
it[url] = source.
|
||||
it[url] = fetchedEpisode.url
|
||||
it[name] = fetchedEpisode.name
|
||||
it[date_upload] = fetchedEpisode.date_upload
|
||||
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 */
|
||||
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 */
|
||||
|
@ -27,9 +27,9 @@ data class EpisodeDataClass(
|
||||
/** this chapter's index, starts with 1 */
|
||||
val index: Int,
|
||||
|
||||
/** total chapter count, used to calculate if there's a next and prev chapter */
|
||||
val chapterCount: Int? = null,
|
||||
/** total episode count, used to calculate if there's a next and prev episode */
|
||||
val episodeCount: Int? = null,
|
||||
|
||||
/** used to construct pages in the front-end */
|
||||
val pageCount: Int? = null,
|
||||
val linkUrl: String? = null,
|
||||
)
|
||||
|
@ -9,6 +9,8 @@ package suwayomi.anime.model.table
|
||||
|
||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||
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
|
||||
|
||||
object EpisodeTable : IntIdTable() {
|
||||
@ -40,4 +42,5 @@ fun EpisodeTable.toDataClass(episodeEntry: ResultRow) =
|
||||
episodeEntry[isBookmarked],
|
||||
episodeEntry[lastPageRead],
|
||||
episodeEntry[episodeIndex],
|
||||
transaction { EpisodeTable.select { anime eq episodeEntry[anime] }.count().toInt() }
|
||||
)
|
||||
|
@ -32,6 +32,7 @@ import MangaExtensions from 'screens/manga/MangaExtensions';
|
||||
import SourceMangas from 'screens/manga/SourceMangas';
|
||||
import SourceAnimes from 'screens/anime/SourceAnimes';
|
||||
import Reader from 'screens/manga/Reader';
|
||||
import Player from 'screens/anime/Player';
|
||||
import AnimeExtensions from 'screens/anime/AnimeExtensions';
|
||||
|
||||
export default function App() {
|
||||
@ -133,13 +134,6 @@ export default function App() {
|
||||
<Route path="/library">
|
||||
<Library />
|
||||
</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 */}
|
||||
<Route path="/anime/extensions">
|
||||
@ -154,11 +148,21 @@ export default function App() {
|
||||
<Route path="/anime/sources">
|
||||
<AnimeSources />
|
||||
</Route>
|
||||
<Route path="/anime/:animeId/episode/:episodeIndex">
|
||||
<Player />
|
||||
</Route>
|
||||
<Route path="/anime/:id">
|
||||
<Anime />
|
||||
</Route>
|
||||
</Switch>
|
||||
</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>
|
||||
</ThemeProvider>
|
||||
</Router>
|
||||
|
77
webUI/react/src/screens/anime/Player.tsx
Normal file
77
webUI/react/src/screens/anime/Player.tsx
Normal 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>
|
||||
);
|
||||
}
|
10
webUI/react/src/typings.d.ts
vendored
10
webUI/react/src/typings.d.ts
vendored
@ -55,7 +55,6 @@ interface IManga {
|
||||
}
|
||||
|
||||
interface IChapter {
|
||||
id: number
|
||||
url: string
|
||||
name: string
|
||||
uploadDate: number
|
||||
@ -71,7 +70,6 @@ interface IChapter {
|
||||
}
|
||||
|
||||
interface IEpisode {
|
||||
id: number
|
||||
url: string
|
||||
name: string
|
||||
uploadDate: number
|
||||
@ -83,7 +81,7 @@ interface IEpisode {
|
||||
lastPageRead: number
|
||||
index: number
|
||||
episodeCount: number
|
||||
pageCount: number
|
||||
linkUrl: string
|
||||
}
|
||||
|
||||
interface IPartialChpter {
|
||||
@ -92,6 +90,12 @@ interface IPartialChpter {
|
||||
chapterCount: number
|
||||
}
|
||||
|
||||
interface IPartialEpisode {
|
||||
linkUrl: string
|
||||
index: number
|
||||
episodeCount: number
|
||||
}
|
||||
|
||||
interface ICategory {
|
||||
id: number
|
||||
order: number
|
||||
|
Loading…
Reference in New Issue
Block a user