S{{this.item.seasonNumber}}:E{{this.item.episodeNumber}} - {{this.item.title}}
-
-
-
-
-
-
-
- 00:00 / --:--
-
-
-
-
-
-
+
+ GetPeople(long showID);
List GetGenreForShow(long showID);
List GetSeasons(long showID);
+ int GetSeasonCount(string showSlug, long seasonNumber);
(VideoStream video, List audios, List subtitles) GetStreams(long episodeID);
//Public read
@@ -22,7 +23,7 @@ namespace Kyoo.InternalAPI
Season GetSeason(string showSlug, long seasonNumber);
List GetEpisodes(string showSlug, long seasonNumber);
Episode GetEpisode(string showSlug, long seasonNumber, long episodeNumber);
- WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber);
+ WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber, bool complete = true);
People GetPeopleBySlug(string slug);
Genre GetGenreBySlug(string slug);
Studio GetStudioBySlug(string slug);
diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs
index be3958fd..55870ab8 100644
--- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs
+++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs
@@ -1,6 +1,7 @@
using Kyoo.Models;
using Kyoo.Models.Watch;
using Microsoft.Extensions.Configuration;
+using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Diagnostics;
@@ -233,7 +234,7 @@ namespace Kyoo.InternalAPI
public List GetSeasons(long showID)
{
- string query = "SELECT * FROM seasons WHERE showID = $showID;";
+ string query = "SELECT * FROM seasons WHERE showID = $showID ORDER BY seasonNumber;";
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
{
@@ -266,9 +267,23 @@ namespace Kyoo.InternalAPI
}
}
+ public int GetSeasonCount(string showSlug, long seasonNumber)
+ {
+ string query = "SELECT count(episodes.id) FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber;";
+
+ using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
+ {
+ cmd.Parameters.AddWithValue("$showSlug", showSlug);
+ cmd.Parameters.AddWithValue("$seasonNumber", seasonNumber);
+
+ int count = Convert.ToInt32(cmd.ExecuteScalar());
+ return count;
+ }
+ }
+
public List GetEpisodes(string showSlug, long seasonNumber)
{
- string query = "SELECT * FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber;";
+ string query = "SELECT * FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber ORDER BY episodeNumber;";
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
{
@@ -303,7 +318,7 @@ namespace Kyoo.InternalAPI
}
}
- public WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber)
+ public WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber, bool complete = true)
{
string query = "SELECT episodes.id, shows.title as showTitle, shows.slug as showSlug, seasonNumber, episodeNumber, episodes.title, releaseDate, episodes.path FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber;";
@@ -315,7 +330,12 @@ namespace Kyoo.InternalAPI
SQLiteDataReader reader = cmd.ExecuteReader();
if (reader.Read())
- return WatchItem.FromReader(reader).SetStreams(this);
+ {
+ if (complete)
+ return WatchItem.FromReader(reader).SetStreams(this).SetPrevious(this).SetNext(this);
+ else
+ return WatchItem.FromReader(reader);
+ }
else
return null;
}
diff --git a/Kyoo/Models/Episode.cs b/Kyoo/Models/Episode.cs
index 4b9dd3ad..dcd47b61 100644
--- a/Kyoo/Models/Episode.cs
+++ b/Kyoo/Models/Episode.cs
@@ -21,6 +21,7 @@ namespace Kyoo.Models
[JsonIgnore] public string ImgPrimary;
public string ExternalIDs;
+ public string Link; //Used only on the player
public string Thumb; //Used in the API response only
@@ -77,6 +78,7 @@ namespace Kyoo.Models
public Episode SetThumb(string showSlug)
{
Thumb = "thumb/" + showSlug + "/s" + seasonNumber + "/e" + episodeNumber;
+ Link = showSlug + "-s" + seasonNumber + "e" + episodeNumber;
return this;
}
}
diff --git a/Kyoo/Models/WatchItem.cs b/Kyoo/Models/WatchItem.cs
index 52e1dc48..d7fcfe3b 100644
--- a/Kyoo/Models/WatchItem.cs
+++ b/Kyoo/Models/WatchItem.cs
@@ -15,8 +15,11 @@ namespace Kyoo.Models
public long seasonNumber;
public long episodeNumber;
public string Title;
+ public string Link;
public DateTime? ReleaseDate;
[JsonIgnore] public string Path;
+ public string previousEpisode;
+ public Episode nextEpisode;
[JsonIgnore] public VideoStream video;
public IEnumerable audios;
@@ -34,6 +37,8 @@ namespace Kyoo.Models
Title = title;
ReleaseDate = releaseDate;
Path = path;
+
+ Link = ShowSlug + "-s" + seasonNumber + "e" + episodeNumber;
}
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Stream[] audios, Stream[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path)
@@ -62,5 +67,29 @@ namespace Kyoo.Models
subtitles = streams.subtitles;
return this;
}
+
+ public WatchItem SetPrevious(ILibraryManager libraryManager)
+ {
+ long lastEp = episodeNumber - 1;
+ if(lastEp > 0)
+ previousEpisode = ShowSlug + "-s" + seasonNumber + "e" + lastEp;
+ else if(seasonNumber > 1)
+ {
+ int seasonCount = libraryManager.GetSeasonCount(ShowSlug, seasonNumber - 1);
+ previousEpisode = ShowSlug + "-s" + (seasonNumber - 1) + "e" + seasonCount;
+ }
+ return this;
+ }
+
+ public WatchItem SetNext(ILibraryManager libraryManager)
+ {
+ long seasonCount = libraryManager.GetSeasonCount(ShowSlug, seasonNumber);
+ if (episodeNumber >= seasonCount)
+ nextEpisode = libraryManager.GetEpisode(ShowSlug, seasonNumber + 1, 1);
+ else
+ nextEpisode = libraryManager.GetEpisode(ShowSlug, seasonNumber, episodeNumber + 1);
+
+ return this;
+ }
}
}
+
diff --git a/Kyoo/ClientApp/src/app/player/player.component.scss b/Kyoo/ClientApp/src/app/player/player.component.scss
index 796ca57d..c9d2926d 100644
--- a/Kyoo/ClientApp/src/app/player/player.component.scss
+++ b/Kyoo/ClientApp/src/app/player/player.component.scss
@@ -114,3 +114,62 @@
}
}
}
+
+
+#nextBtn
+{
+ position: relative;
+
+ &:hover
+ {
+ #next
+ {
+ display: flex;
+ }
+ }
+
+ #next
+ {
+ position: absolute;
+ left: 0;
+ bottom: 100%;
+ display: none;
+ background-color: #212121;
+ white-space: normal;
+ line-height: normal;
+ cursor: default;
+ height: 150px;
+
+ #main
+ {
+ width: auto;
+ height: 100%;
+ flex-shrink: 0;
+ flex-grow: 0;
+
+ > img
+ {
+ width: auto;
+ height: 100%;
+ }
+ }
+
+ #overview
+ {
+ padding: 1%;
+ width: 50%;
+ min-width: 300px;
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+
+ > p
+ {
+ text-align: justify;
+ font-weight: 300;
+ overflow: hidden;
+ margin: 0;
+ }
+ }
+ }
+}
diff --git a/Kyoo/ClientApp/src/app/player/player.component.ts b/Kyoo/ClientApp/src/app/player/player.component.ts
index fa6a1871..0d170398 100644
--- a/Kyoo/ClientApp/src/app/player/player.component.ts
+++ b/Kyoo/ClientApp/src/app/player/player.component.ts
@@ -12,22 +12,31 @@ import { Location } from "@angular/common";
export class PlayerComponent implements OnInit
{
item: WatchItem;
- video: string;
+
+ hours: number;
+ minutes: number;
+ seconds: number;
playIcon: string = "pause"; //Icon used by the play btn.
fullscreenIcon: string = "fullscreen"; //Icon used by the fullscreen btn.
private player: HTMLVideoElement;
- constructor(private route: ActivatedRoute, private location: Location)
- {
- this.video = this.route.snapshot.paramMap.get("item");
- }
+ constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer, private location: Location) { }
ngOnInit()
{
document.getElementById("nav").classList.add("d-none");
- this.item = this.route.snapshot.data.item;
+ this.route.data.subscribe((data) =>
+ {
+ this.item = data.item;
+
+ if (this.player)
+ {
+ this.player.load();
+ this.initPlayBtn();
+ }
+ });
console.log("Init");
}
@@ -36,9 +45,25 @@ export class PlayerComponent implements OnInit
this.player = document.getElementById("player") as HTMLVideoElement;
this.player.controls = false;
- $('[data-toggle="tooltip"]').tooltip();
+ this.player.onplay = () =>
+ {
+ this.initPlayBtn();
+ }
- document.addEventListener("fullscreenchange", (event) =>
+ this.player.onpause = () =>
+ {
+ this.playIcon = "play_arrow"
+ $("#play").attr("data-original-title", "Play").tooltip("show");
+ }
+
+ this.player.ontimeupdate = () =>
+ {
+ this.seconds = Math.round(this.player.currentTime % 60);
+ this.minutes = Math.round(this.player.currentTime / 60);
+ };
+
+
+ document.addEventListener("fullscreenchange", () =>
{
if (document.fullscreenElement != null)
{
@@ -51,6 +76,8 @@ export class PlayerComponent implements OnInit
$("#fullscreen").attr("data-original-title", "Fullscreen").tooltip("show");
}
});
+
+ $('[data-toggle="tooltip"]').tooltip();
}
ngOnDestroy()
@@ -65,22 +92,16 @@ export class PlayerComponent implements OnInit
tooglePlayback()
{
- let playBtn: HTMLElement = document.getElementById("play");
-
if (this.player.paused)
- {
this.player.play();
-
- this.playIcon = "pause"
- $(playBtn).attr("data-original-title", "Pause").tooltip("show");
- }
else
- {
this.player.pause();
+ }
- this.playIcon = "play_arrow"
- $(playBtn).attr("data-original-title", "Play").tooltip("show");
- }
+ initPlayBtn()
+ {
+ this.playIcon = "pause";
+ $("#play").attr("data-original-title", "Pause").tooltip("show");
}
fullscreen()
@@ -90,4 +111,10 @@ export class PlayerComponent implements OnInit
else
document.exitFullscreen();
}
+
+
+ getThumb(url: string)
+ {
+ return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")");
+ }
}
diff --git a/Kyoo/ClientApp/src/main.ts b/Kyoo/ClientApp/src/main.ts
index c7b673cf..c8d4c2ad 100644
--- a/Kyoo/ClientApp/src/main.ts
+++ b/Kyoo/ClientApp/src/main.ts
@@ -3,6 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
+import "hammerjs"
if (environment.production) {
enableProdMode();
diff --git a/Kyoo/ClientApp/src/models/episode.js b/Kyoo/ClientApp/src/models/episode.js
new file mode 100644
index 00000000..b651890b
--- /dev/null
+++ b/Kyoo/ClientApp/src/models/episode.js
@@ -0,0 +1,3 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+//# sourceMappingURL=episode.js.map
\ No newline at end of file
diff --git a/Kyoo/ClientApp/src/models/episode.js.map b/Kyoo/ClientApp/src/models/episode.js.map
new file mode 100644
index 00000000..edbd2c6e
--- /dev/null
+++ b/Kyoo/ClientApp/src/models/episode.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"episode.js","sourceRoot":"","sources":["episode.ts"],"names":[],"mappings":""}
\ No newline at end of file
diff --git a/Kyoo/ClientApp/src/models/episode.ts b/Kyoo/ClientApp/src/models/episode.ts
index fbb7c7c1..64093ab3 100644
--- a/Kyoo/ClientApp/src/models/episode.ts
+++ b/Kyoo/ClientApp/src/models/episode.ts
@@ -2,6 +2,8 @@ export interface Episode
{
episodeNumber: number;
title: string;
+ thumb: string;
+ link: string;
overview: string;
releaseDate;
runtimeInMinutes: number;
diff --git a/Kyoo/ClientApp/src/models/watch-item.ts b/Kyoo/ClientApp/src/models/watch-item.ts
index eaa07a55..266280f7 100644
--- a/Kyoo/ClientApp/src/models/watch-item.ts
+++ b/Kyoo/ClientApp/src/models/watch-item.ts
@@ -1,11 +1,19 @@
+import { Episode } from "./episode";
+
export interface WatchItem
{
showTitle: string;
showSlug: string;
seasonNumber: number;
episodeNumber: number;
+ video: string;
title: string;
+ link: string;
releaseDate;
+
+ previousEpisode: string;
+ nextEpisode: Episode;
+
audio: Stream[];
subtitles: Stream[];
}
diff --git a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs
index a1cefe6b..79c9ac2e 100644
--- a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs
+++ b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs
@@ -14,6 +14,7 @@ namespace Kyoo.InternalAPI
List
+
+
+
+
+
S{{this.item.seasonNumber}}:E{{this.item.episodeNumber}} - {{this.item.title}}
+
+
+
+
+
+
+
+ {{this.minutes | number: '2.0-0'}}:{{this.seconds | number: '2.0-0'}} / --:--
+
+
+
+
+
+
+