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) 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)

View File

@ -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,6 +85,7 @@
</div> </div>
<mat-menu #subtitles="matMenu"> <mat-menu #subtitles="matMenu">
<ng-template matMenuContent>
<button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)"> <button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)">
<span>None</span> <span>None</span>
</button> </button>
@ -100,6 +101,7 @@
</button> </button>
</ng-template> </ng-template>
</div> </div>
</ng-template>
</mat-menu> </mat-menu>
</div> </div>
</div> </div>

View File

@ -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;
}
} }
} }
} }

View File

@ -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 + ")");

View File

@ -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>

View File

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

View File

@ -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;

View File

@ -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

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;"; 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;
} }

View File

@ -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;
}
} }
} }

View File

@ -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;
}
} }
} }

View File

@ -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;