Reworking player flow with subtitles and end user subtitles.

This commit is contained in:
Zoe Roux 2019-09-17 18:04:01 +02:00
parent 735c4098a3
commit d17e598a18
12 changed files with 104 additions and 61 deletions

View File

@ -30,7 +30,7 @@ export class BrowseComponent implements OnInit
getThumb(slug: string)
{
return this.sanitizer.bypassSecurityTrustStyle("url(/thumb/" + slug + ")");
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
}
sort(type: string, order: boolean)

View File

@ -35,13 +35,13 @@
<div class="buttons">
<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>
</button>
</a>
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Play" id="play" (click)="tooglePlayback()">
<mat-icon>{{this.playIcon}}</mat-icon>
</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>
<div id="next">
@ -53,7 +53,7 @@
<p>{{this.item.nextEpisode.overview}}</p>
</div>
</div>
</button>
</a>
<div id="volume">
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Volume" (click)="toogleMute()">
<mat-icon>{{this.volumeIcon}}</mat-icon>
@ -85,21 +85,23 @@
</div>
<mat-menu #subtitles="matMenu">
<button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)">
<span>None</span>
</button>
<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>
<ng-template matMenuContent>
<button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)">
<span>None</span>
</button>
<ng-template #elseBlock>
<button mat-menu-item disabled>
<span>{{subtitle.displayName}} ({{subtitle.codec}})</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>
</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>
</div>
</div>

View File

@ -120,6 +120,15 @@
margin-right: .3rem;
outline: none;
}
> a
{
margin-left: .3rem;
margin-right: .3rem;
outline: none;
color: inherit;
text-decoration: inherit;
}
}
}
}

View File

@ -1,6 +1,6 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
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 { Location } from "@angular/common";
import { MatSliderChange } from "@angular/material/slider";
@ -39,7 +39,7 @@ export class PlayerComponent implements OnInit
private progress: 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()
{
@ -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" });
}
@ -228,11 +238,6 @@ export class PlayerComponent implements OnInit
$('[data-toggle="tooltip"]').hide();
}
back()
{
this.location.back();
}
tooglePlayback()
{
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;
if (subtitle == null)
{
console.log("Removing subtitle");
SubtitleManager.remove(this.player);
}
else
{
console.log("Loading subtitle: " + subtitle.displayName);
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)
{
return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")");

View File

@ -4,7 +4,7 @@
<div class="header container pt-sm-5">
<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="info">
<h1 class="title">{{this.show.title}}</h1>

View File

@ -27,4 +27,5 @@ export interface Track
isDefault: boolean;
isForced: boolean;
codec: string;
link: string;
}

View File

@ -17,7 +17,7 @@ namespace Kyoo.Controllers
peoplePath = config.GetValue<string>("peoplePath");
}
[HttpGet("thumb/{showSlug}")]
[HttpGet("poster/{showSlug}")]
public IActionResult GetShowThumb(string showSlug)
{
string path = libraryManager.GetShowBySlug(showSlug)?.Path;
@ -72,7 +72,7 @@ namespace Kyoo.Controllers
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)
{
string path = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber)?.Path;

View File

@ -17,7 +17,7 @@ namespace Kyoo.InternalAPI
int GetSeasonCount(string showSlug, long seasonNumber);
//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);
//Public read

View File

@ -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;";
@ -203,7 +203,7 @@ namespace Kyoo.InternalAPI
while (reader.Read())
{
Track track = Track.FromReader(reader);
Track track = Track.FromReader(reader).SetLink(episodeSlug);
if (track.type == StreamType.Audio)
audios.Add(track);
@ -229,7 +229,7 @@ namespace Kyoo.InternalAPI
SQLiteDataReader reader = cmd.ExecuteReader();
if (reader.Read())
return Track.FromReader(reader);
return Track.FromReader(reader).SetLink(Episode.GetSlug(showSlug, seasonNumber, episodeNumber));
return null;
}

View File

@ -77,9 +77,14 @@ namespace Kyoo.Models
public Episode SetThumb(string showSlug)
{
Thumb = "thumb/" + showSlug + "/s" + seasonNumber + "/e" + episodeNumber;
Link = showSlug + "-s" + seasonNumber + "e" + episodeNumber;
Link = GetSlug(showSlug, seasonNumber, episodeNumber);
Thumb = "thumb/" + Link;
return this;
}
public static string GetSlug(string showSlug, long seasonNumber, long episodeNumber)
{
return showSlug + "-s" + seasonNumber + "e" + episodeNumber;
}
}
}

View File

@ -29,6 +29,8 @@ namespace Kyoo.Models
public class Track : Stream
{
public string DisplayName;
public string Link;
[JsonIgnore] public readonly long id;
[JsonIgnore] public long episodeID;
[JsonIgnore] public StreamType type;
@ -44,14 +46,6 @@ namespace Kyoo.Models
Codec = codec;
IsExternal = isExternal;
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)
@ -75,5 +69,32 @@ namespace Kyoo.Models
{
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;
}
}
}

View File

@ -37,7 +37,7 @@ namespace Kyoo.Models
ReleaseDate = releaseDate;
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)
@ -60,7 +60,7 @@ namespace Kyoo.Models
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;
subtitles = streams.subtitles;
return this;