mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-08 10:44:20 -04:00
Reworking player flow with subtitles and end user subtitles.
This commit is contained in:
parent
735c4098a3
commit
d17e598a18
@ -30,7 +30,7 @@ export class BrowseComponent implements OnInit
|
|||||||
|
|
||||||
getThumb(slug: string)
|
getThumb(slug: string)
|
||||||
{
|
{
|
||||||
return this.sanitizer.bypassSecurityTrustStyle("url(/thumb/" + slug + ")");
|
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
sort(type: string, order: boolean)
|
sort(type: string, order: boolean)
|
||||||
|
@ -35,13 +35,13 @@
|
|||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<button *ngIf="this.item.previousEpisode" mat-icon-button data-toggle="tooltip" data-placement="top" title="Previous" routerLink="/watch/{{this.item.previousEpisode}}">
|
<a *ngIf="this.item.previousEpisode" mat-icon-button data-toggle="tooltip" data-placement="top" title="Previous" routerLink="/watch/{{this.item.previousEpisode}}" href="/watch/{{this.item.previousEpisode}}" queryParamsHandling="merge">
|
||||||
<mat-icon>skip_previous</mat-icon>
|
<mat-icon>skip_previous</mat-icon>
|
||||||
</button>
|
</a>
|
||||||
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Play" id="play" (click)="tooglePlayback()">
|
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Play" id="play" (click)="tooglePlayback()">
|
||||||
<mat-icon>{{this.playIcon}}</mat-icon>
|
<mat-icon>{{this.playIcon}}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button mat-icon-button id="nextBtn" *ngIf="this.item.nextEpisode" routerLink="/watch/{{this.item.nextEpisode.link}}">
|
<a mat-icon-button id="nextBtn" *ngIf="this.item.nextEpisode" routerLink="/watch/{{this.item.nextEpisode.link}}" href="/watch/{{this.item.nextEpisode.link}}" queryParamsHandling="merge">
|
||||||
<mat-icon>skip_next</mat-icon>
|
<mat-icon>skip_next</mat-icon>
|
||||||
|
|
||||||
<div id="next">
|
<div id="next">
|
||||||
@ -53,7 +53,7 @@
|
|||||||
<p>{{this.item.nextEpisode.overview}}</p>
|
<p>{{this.item.nextEpisode.overview}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</a>
|
||||||
<div id="volume">
|
<div id="volume">
|
||||||
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Volume" (click)="toogleMute()">
|
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Volume" (click)="toogleMute()">
|
||||||
<mat-icon>{{this.volumeIcon}}</mat-icon>
|
<mat-icon>{{this.volumeIcon}}</mat-icon>
|
||||||
@ -85,21 +85,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-menu #subtitles="matMenu">
|
<mat-menu #subtitles="matMenu">
|
||||||
<button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)">
|
<ng-template matMenuContent>
|
||||||
<span>None</span>
|
<button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)">
|
||||||
</button>
|
<span>None</span>
|
||||||
|
|
||||||
<div *ngFor="let subtitle of this.item.subtitles">
|
|
||||||
<button [ngClass]="{'selected': this.selectedSubtitle == subtitle}" mat-menu-item *ngIf="subtitle.codec == 'ass'; else elseBlock" (click)="selectSubtitle(subtitle)">
|
|
||||||
<span>{{subtitle.displayName}}</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ng-template #elseBlock>
|
<div *ngFor="let subtitle of this.item.subtitles">
|
||||||
<button mat-menu-item disabled>
|
<button [ngClass]="{'selected': this.selectedSubtitle == subtitle}" mat-menu-item *ngIf="subtitle.codec == 'ass'; else elseBlock" (click)="selectSubtitle(subtitle)">
|
||||||
<span>{{subtitle.displayName}} ({{subtitle.codec}})</span>
|
<span>{{subtitle.displayName}}</span>
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
|
||||||
</div>
|
<ng-template #elseBlock>
|
||||||
|
<button mat-menu-item disabled>
|
||||||
|
<span>{{subtitle.displayName}} ({{subtitle.codec}})</span>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,6 +120,15 @@
|
|||||||
margin-right: .3rem;
|
margin-right: .3rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> a
|
||||||
|
{
|
||||||
|
margin-left: .3rem;
|
||||||
|
margin-right: .3rem;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { WatchItem, Track } from "../../models/watch-item";
|
import { WatchItem, Track } from "../../models/watch-item";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { DomSanitizer, Title } from "@angular/platform-browser";
|
import { DomSanitizer, Title } from "@angular/platform-browser";
|
||||||
import { Location } from "@angular/common";
|
import { Location } from "@angular/common";
|
||||||
import { MatSliderChange } from "@angular/material/slider";
|
import { MatSliderChange } from "@angular/material/slider";
|
||||||
@ -39,7 +39,7 @@ export class PlayerComponent implements OnInit
|
|||||||
private progress: HTMLElement;
|
private progress: HTMLElement;
|
||||||
private buffered: HTMLElement;
|
private buffered: HTMLElement;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer, private location: Location, private title: Title) { }
|
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer, private location: Location, private title: Title, private router: Router) { }
|
||||||
|
|
||||||
ngOnInit()
|
ngOnInit()
|
||||||
{
|
{
|
||||||
@ -192,6 +192,16 @@ export class PlayerComponent implements OnInit
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Load sub selected from the url.
|
||||||
|
let sub: string = this.route.snapshot.queryParams["sub"];
|
||||||
|
if (sub != null)
|
||||||
|
{
|
||||||
|
let languageCode: string = sub.substring(0, 3);
|
||||||
|
let forced: boolean = sub.length > 3 && sub.substring(4) == "for";
|
||||||
|
|
||||||
|
this.selectSubtitle(this.item.subtitles.find(x => x.language == languageCode && x.isForced == forced), false);
|
||||||
|
}
|
||||||
|
|
||||||
$('[data-toggle="tooltip"]').tooltip({ trigger: "hover" });
|
$('[data-toggle="tooltip"]').tooltip({ trigger: "hover" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,11 +238,6 @@ export class PlayerComponent implements OnInit
|
|||||||
$('[data-toggle="tooltip"]').hide();
|
$('[data-toggle="tooltip"]').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
back()
|
|
||||||
{
|
|
||||||
this.location.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
tooglePlayback()
|
tooglePlayback()
|
||||||
{
|
{
|
||||||
if (this.player.paused)
|
if (this.player.paused)
|
||||||
@ -293,37 +298,37 @@ export class PlayerComponent implements OnInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectSubtitle(subtitle: Track)
|
selectSubtitle(subtitle: Track, changeUrl: boolean = true)
|
||||||
{
|
{
|
||||||
|
if (changeUrl)
|
||||||
|
{
|
||||||
|
let subSlug: string;
|
||||||
|
if (subtitle != null)
|
||||||
|
{
|
||||||
|
subSlug = subtitle.language;
|
||||||
|
if (subtitle.isForced)
|
||||||
|
subSlug += "-for";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.router.navigate([], { relativeTo: this.route, queryParams: { sub: subSlug }, replaceUrl: true, queryParamsHandling: "merge" });
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedSubtitle = subtitle;
|
this.selectedSubtitle = subtitle;
|
||||||
|
|
||||||
if (subtitle == null)
|
if (subtitle == null)
|
||||||
{
|
{
|
||||||
|
console.log("Removing subtitle");
|
||||||
SubtitleManager.remove(this.player);
|
SubtitleManager.remove(this.player);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
console.log("Loading subtitle: " + subtitle.displayName);
|
||||||
|
|
||||||
if (subtitle.codec == "ass")
|
if (subtitle.codec == "ass")
|
||||||
SubtitleManager.add(this.player, this.getSubtitleLink(subtitle), true);
|
SubtitleManager.add(this.player, subtitle.link, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubtitleLink(subtitle: Track): string
|
|
||||||
{
|
|
||||||
let link: string = "/api/subtitle/" + this.item.link + "." + subtitle.language;
|
|
||||||
|
|
||||||
if (subtitle.isForced)
|
|
||||||
link += "-forced";
|
|
||||||
|
|
||||||
//The extension is not necesarry but we add this because it allow the user to quickly download the file in the good format if he wants.
|
|
||||||
if (subtitle.codec == "ass")
|
|
||||||
link += ".ass";
|
|
||||||
else if (subtitle.codec == "subrip")
|
|
||||||
link += ".srt"
|
|
||||||
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
getThumb(url: string)
|
getThumb(url: string)
|
||||||
{
|
{
|
||||||
return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")");
|
return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")");
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<div class="header container pt-sm-5">
|
<div class="header container pt-sm-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<img class="poster d-none d-sm-block" src="thumb/{{this.show.slug}}" />
|
<img class="poster d-none d-sm-block" src="poster/{{this.show.slug}}" />
|
||||||
<div class="main col">
|
<div class="main col">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<h1 class="title">{{this.show.title}}</h1>
|
<h1 class="title">{{this.show.title}}</h1>
|
||||||
|
@ -27,4 +27,5 @@ export interface Track
|
|||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
isForced: boolean;
|
isForced: boolean;
|
||||||
codec: string;
|
codec: string;
|
||||||
|
link: string;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace Kyoo.Controllers
|
|||||||
peoplePath = config.GetValue<string>("peoplePath");
|
peoplePath = config.GetValue<string>("peoplePath");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("thumb/{showSlug}")]
|
[HttpGet("poster/{showSlug}")]
|
||||||
public IActionResult GetShowThumb(string showSlug)
|
public IActionResult GetShowThumb(string showSlug)
|
||||||
{
|
{
|
||||||
string path = libraryManager.GetShowBySlug(showSlug)?.Path;
|
string path = libraryManager.GetShowBySlug(showSlug)?.Path;
|
||||||
@ -72,7 +72,7 @@ namespace Kyoo.Controllers
|
|||||||
return new PhysicalFileResult(thumbPath, "image/jpg");
|
return new PhysicalFileResult(thumbPath, "image/jpg");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("thumb/{showSlug}/s{seasonNumber}/e{episodeNumber}")]
|
[HttpGet("thumb/{showSlug}-s{seasonNumber}e{episodeNumber}")]
|
||||||
public IActionResult GetEpisodeThumb(string showSlug, long seasonNumber, long episodeNumber)
|
public IActionResult GetEpisodeThumb(string showSlug, long seasonNumber, long episodeNumber)
|
||||||
{
|
{
|
||||||
string path = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber)?.Path;
|
string path = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber)?.Path;
|
||||||
|
@ -17,7 +17,7 @@ namespace Kyoo.InternalAPI
|
|||||||
int GetSeasonCount(string showSlug, long seasonNumber);
|
int GetSeasonCount(string showSlug, long seasonNumber);
|
||||||
|
|
||||||
//Internal HTML read
|
//Internal HTML read
|
||||||
(List<Track> audios, List<Track> subtitles) GetStreams(long episodeID);
|
(List<Track> audios, List<Track> subtitles) GetStreams(long episodeID, string showSlug);
|
||||||
Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, bool forced);
|
Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, bool forced);
|
||||||
|
|
||||||
//Public read
|
//Public read
|
||||||
|
@ -189,7 +189,7 @@ namespace Kyoo.InternalAPI
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public (List<Track> audios, List<Track> subtitles) GetStreams(long episodeID)
|
public (List<Track> audios, List<Track> subtitles) GetStreams(long episodeID, string episodeSlug)
|
||||||
{
|
{
|
||||||
string query = "SELECT * FROM tracks WHERE episodeID = $episodeID;";
|
string query = "SELECT * FROM tracks WHERE episodeID = $episodeID;";
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ namespace Kyoo.InternalAPI
|
|||||||
|
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
Track track = Track.FromReader(reader);
|
Track track = Track.FromReader(reader).SetLink(episodeSlug);
|
||||||
|
|
||||||
if (track.type == StreamType.Audio)
|
if (track.type == StreamType.Audio)
|
||||||
audios.Add(track);
|
audios.Add(track);
|
||||||
@ -229,7 +229,7 @@ namespace Kyoo.InternalAPI
|
|||||||
SQLiteDataReader reader = cmd.ExecuteReader();
|
SQLiteDataReader reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
if (reader.Read())
|
if (reader.Read())
|
||||||
return Track.FromReader(reader);
|
return Track.FromReader(reader).SetLink(Episode.GetSlug(showSlug, seasonNumber, episodeNumber));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -77,9 +77,14 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Episode SetThumb(string showSlug)
|
public Episode SetThumb(string showSlug)
|
||||||
{
|
{
|
||||||
Thumb = "thumb/" + showSlug + "/s" + seasonNumber + "/e" + episodeNumber;
|
Link = GetSlug(showSlug, seasonNumber, episodeNumber);
|
||||||
Link = showSlug + "-s" + seasonNumber + "e" + episodeNumber;
|
Thumb = "thumb/" + Link;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetSlug(string showSlug, long seasonNumber, long episodeNumber)
|
||||||
|
{
|
||||||
|
return showSlug + "-s" + seasonNumber + "e" + episodeNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,8 @@ namespace Kyoo.Models
|
|||||||
public class Track : Stream
|
public class Track : Stream
|
||||||
{
|
{
|
||||||
public string DisplayName;
|
public string DisplayName;
|
||||||
|
public string Link;
|
||||||
|
|
||||||
[JsonIgnore] public readonly long id;
|
[JsonIgnore] public readonly long id;
|
||||||
[JsonIgnore] public long episodeID;
|
[JsonIgnore] public long episodeID;
|
||||||
[JsonIgnore] public StreamType type;
|
[JsonIgnore] public StreamType type;
|
||||||
@ -44,14 +46,6 @@ namespace Kyoo.Models
|
|||||||
Codec = codec;
|
Codec = codec;
|
||||||
IsExternal = isExternal;
|
IsExternal = isExternal;
|
||||||
Path = path;
|
Path = path;
|
||||||
|
|
||||||
//Converting mkv track language to c# system language tag.
|
|
||||||
if (language == "fre")
|
|
||||||
language = "fra";
|
|
||||||
|
|
||||||
DisplayName = CultureInfo.GetCultures(CultureTypes.NeutralCultures).Where(x => x.ThreeLetterISOLanguageName == language).FirstOrDefault()?.DisplayName ?? language;
|
|
||||||
if (Title != null && Title.Length > 1)
|
|
||||||
DisplayName += " - " + Title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Track FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
public static Track FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
||||||
@ -75,5 +69,32 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
return new Track(type, stream.Title, stream.Language, stream.IsDefault, stream.IsForced, stream.Codec, false, stream.Path);
|
return new Track(type, stream.Title, stream.Language, stream.IsDefault, stream.IsForced, stream.Codec, false, stream.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Track SetLink(string episodeSlug)
|
||||||
|
{
|
||||||
|
string language = Language;
|
||||||
|
//Converting mkv track language to c# system language tag.
|
||||||
|
if (language == "fre")
|
||||||
|
language = "fra";
|
||||||
|
|
||||||
|
DisplayName = CultureInfo.GetCultures(CultureTypes.NeutralCultures).Where(x => x.ThreeLetterISOLanguageName == language).FirstOrDefault()?.DisplayName ?? language;
|
||||||
|
Link = "/api/subtitle/" + episodeSlug + "." + Language;
|
||||||
|
|
||||||
|
if (IsForced)
|
||||||
|
{
|
||||||
|
DisplayName += " Forced";
|
||||||
|
Link += "-forced";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Title != null && Title.Length > 1)
|
||||||
|
DisplayName += " - " + Title;
|
||||||
|
|
||||||
|
if (Codec == "ass")
|
||||||
|
Link += ".ass";
|
||||||
|
else if (Codec == "subrip")
|
||||||
|
Link += ".srt";
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,7 +37,7 @@ namespace Kyoo.Models
|
|||||||
ReleaseDate = releaseDate;
|
ReleaseDate = releaseDate;
|
||||||
Path = path;
|
Path = path;
|
||||||
|
|
||||||
Link = ShowSlug + "-s" + seasonNumber + "e" + episodeNumber;
|
Link = Episode.GetSlug(ShowSlug, seasonNumber, episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Track[] audios, Track[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path)
|
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Track[] audios, Track[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path)
|
||||||
@ -60,7 +60,7 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public WatchItem SetStreams(ILibraryManager libraryManager)
|
public WatchItem SetStreams(ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
(IEnumerable<Track> audios, IEnumerable<Track> subtitles) streams = libraryManager.GetStreams(episodeID);
|
(IEnumerable<Track> audios, IEnumerable<Track> subtitles) streams = libraryManager.GetStreams(episodeID, Link);
|
||||||
audios = streams.audios;
|
audios = streams.audios;
|
||||||
subtitles = streams.subtitles;
|
subtitles = streams.subtitles;
|
||||||
return this;
|
return this;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user