Adding subtitle swap fonctionality inside the web app player.

This commit is contained in:
Zoe Roux 2019-09-17 02:39:26 +02:00
parent 1ce584d087
commit 7cf9a6fe6b
7 changed files with 83 additions and 22 deletions

View File

@ -64,7 +64,7 @@
<button *ngIf="this.item.audios.length > 0" mat-icon-button data-toggle="tooltip" data-placement="top" title="Select audio track"> <button *ngIf="this.item.audios.length > 0" mat-icon-button data-toggle="tooltip" data-placement="top" title="Select audio track">
<mat-icon>music_note</mat-icon> <mat-icon>music_note</mat-icon>
</button> </button>
<button *ngIf="this.item.subtitles.length > 0" mat-icon-button data-toggle="tooltip" data-placement="top" title="Select subtitle track"> <button *ngIf="this.item.subtitles.length > 0" mat-icon-button [matMenuTriggerFor]="subtitles" data-toggle="tooltip" data-placement="top" title="Select subtitle track">
<mat-icon>closed_caption</mat-icon> <mat-icon>closed_caption</mat-icon>
</button> </button>
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Cast"> <button mat-icon-button data-toggle="tooltip" data-placement="top" title="Cast">
@ -79,6 +79,24 @@
</div> </div>
</div> </div>
</div> </div>
<mat-menu #subtitles="matMenu">
<button mat-menu-item (click)="selectSubtitle(null)">
<span>None</span>
</button>
<div *ngFor="let subtitle of this.item.subtitles">
<button mat-menu-item *ngIf="subtitle.codec == 'ass'; else elseBlock" (click)="selectSubtitle(subtitle)">
<span>{{subtitle.language}}</span>
</button>
<ng-template #elseBlock>
<button mat-menu-item disabled>
<span>{{subtitle.language}}</span>
</button>
</ng-template>
</div>
</mat-menu>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { WatchItem } from "../../models/watch-item"; import { WatchItem, Track } from "../../models/watch-item";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } 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";
@ -20,6 +20,7 @@ export class PlayerComponent implements OnInit
volume: number = 100; volume: number = 100;
seeking: boolean = false; seeking: boolean = false;
videoHider; videoHider;
selectedSubtitle: Track;
hours: number; hours: number;
minutes: number = 0; minutes: number = 0;
@ -100,7 +101,6 @@ export class PlayerComponent implements OnInit
let progressBar: HTMLElement = document.getElementById("progress-bar") as HTMLElement; let progressBar: HTMLElement = document.getElementById("progress-bar") as HTMLElement;
$(progressBar).click((event) => $(progressBar).click((event) =>
{ {
console.log("Duration: " + this.player.duration);
event.preventDefault(); event.preventDefault();
let time: number = this.getTimeFromSeekbar(progressBar, event.pageX); let time: number = this.getTimeFromSeekbar(progressBar, event.pageX);
this.player.currentTime = time; this.player.currentTime = time;
@ -182,8 +182,6 @@ export class PlayerComponent implements OnInit
}); });
$('[data-toggle="tooltip"]').tooltip({ trigger: "hover" }); $('[data-toggle="tooltip"]').tooltip({ trigger: "hover" });
SubtitleManager.add(this.player, "/api/subtitle/" + this.item.link + "-fre.ass", true);
} }
getTimeFromSeekbar(progressBar: HTMLElement, pageX: number) getTimeFromSeekbar(progressBar: HTMLElement, pageX: number)
@ -281,6 +279,36 @@ export class PlayerComponent implements OnInit
} }
} }
selectSubtitle(subtitle: Track)
{
this.selectedSubtitle = subtitle;
if (subtitle == null)
{
SubtitleManager.remove(this.player);
}
else
{
if (subtitle.codec == "ass")
SubtitleManager.add(this.player, this.getSubtitleLink(subtitle), 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)
{ {

View File

@ -15,15 +15,15 @@ export interface WatchItem
previousEpisode: string; previousEpisode: string;
nextEpisode: Episode; nextEpisode: Episode;
audio: Stream[]; audio: Track[];
subtitles: Stream[]; subtitles: Track[];
} }
export interface Stream export interface Track
{ {
title: string; title: string;
language: string; language: string;
isDefault: boolean; isDefault: boolean;
isForced: boolean; isForced: boolean;
format: string; codec: string;
} }

View File

@ -18,10 +18,23 @@ namespace Kyoo.Controllers
this.transcoder = transcoder; this.transcoder = transcoder;
} }
[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}-{languageTag}.{format?}")] [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}-{languageTag}.{codec?}")]
public IActionResult GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, string format) public IActionResult GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, string codec)
{ {
Track subtitle = libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag); Track subtitle = libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag, false);
if (subtitle == null)
return NotFound();
//Should use appropriate mime type here
return PhysicalFile(subtitle.Path, "text/x-ssa");
}
//This one is never called.
[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}-{languageTag}-{disposition}.{codec?}")] //Disposition can't be tagged as optional because there is a parametter after him.
public IActionResult GetForcedSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, string disposition, string codec)
{
Track subtitle = libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag, disposition == "forced");
if (subtitle == null) if (subtitle == null)
return NotFound(); return NotFound();

View File

@ -18,7 +18,7 @@ namespace Kyoo.InternalAPI
//Internal HTML read //Internal HTML read
(List<Track> audios, List<Track> subtitles) GetStreams(long episodeID); (List<Track> audios, List<Track> subtitles) GetStreams(long episodeID);
Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag); Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, bool forced);
//Public read //Public read
IEnumerable<Library> GetLibraries(); IEnumerable<Library> GetLibraries();

View File

@ -73,7 +73,7 @@ namespace Kyoo.InternalAPI
CREATE TABLE tracks( CREATE TABLE tracks(
id INTEGER PRIMARY KEY UNIQUE, id INTEGER PRIMARY KEY UNIQUE,
episodeID INTEGER, episodeID INTEGER,
streamType TEXT, streamType INTEGER,
title TEXT, title TEXT,
language TEXT, language TEXT,
codec TEXT, codec TEXT,
@ -203,21 +203,21 @@ namespace Kyoo.InternalAPI
while (reader.Read()) while (reader.Read())
{ {
Track stream = Track.FromReader(reader); Track track = Track.FromReader(reader);
if (stream.type == StreamType.Audio) if (track.type == StreamType.Audio)
audios.Add(stream); audios.Add(track);
else if (stream.type == StreamType.Subtitle) else if (track.type == StreamType.Subtitle)
subtitles.Add(stream); subtitles.Add(track);
} }
return (audios, subtitles); return (audios, subtitles);
} }
} }
public Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag) public Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, bool forced)
{ {
string query = "SELECT tracks.* FROM tracks JOIN episodes ON tracks.episodeID = episodes.id JOIN shows ON episodes.showID = shows.id WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber AND tracks.language = $languageTag;"; string query = "SELECT tracks.* FROM tracks JOIN episodes ON tracks.episodeID = episodes.id JOIN shows ON episodes.showID = shows.id WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber AND tracks.language = $languageTag AND tracks.isForced = $forced;";
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
{ {
@ -225,6 +225,7 @@ namespace Kyoo.InternalAPI
cmd.Parameters.AddWithValue("$seasonNumber", seasonNumber); cmd.Parameters.AddWithValue("$seasonNumber", seasonNumber);
cmd.Parameters.AddWithValue("$episodeNumber", episodeNumber); cmd.Parameters.AddWithValue("$episodeNumber", episodeNumber);
cmd.Parameters.AddWithValue("$languageTag", languageTag); cmd.Parameters.AddWithValue("$languageTag", languageTag);
cmd.Parameters.AddWithValue("$forced", forced);
SQLiteDataReader reader = cmd.ExecuteReader(); SQLiteDataReader reader = cmd.ExecuteReader();
if (reader.Read()) if (reader.Read())

View File

@ -1,5 +1,6 @@
using Kyoo.Models.Watch; using Kyoo.Models.Watch;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Kyoo.Models namespace Kyoo.Models
@ -44,7 +45,7 @@ namespace Kyoo.Models
public static Track FromReader(System.Data.SQLite.SQLiteDataReader reader) public static Track FromReader(System.Data.SQLite.SQLiteDataReader reader)
{ {
return new Track(reader["streamType"] as StreamType? ?? StreamType.Unknow, return new Track((StreamType)Enum.ToObject(typeof(StreamType), reader["streamType"]),
reader["title"] as string, reader["title"] as string,
reader["language"] as string, reader["language"] as string,
reader["isDefault"] as bool? ?? false, reader["isDefault"] as bool? ?? false,