Creating subtitle support on the web-app.

This commit is contained in:
Zoe Roux 2019-09-08 02:51:38 +02:00
parent 9527894fad
commit e5d3ea1257
8 changed files with 3489 additions and 11 deletions

View File

@ -31,7 +31,8 @@
], ],
"scripts": [ "scripts": [
"./node_modules/jquery/dist/jquery.min.js", "./node_modules/jquery/dist/jquery.min.js",
"./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" "./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
"./src/libraries/subtitles.js"
] ]
}, },
"configurations": { "configurations": {

View File

@ -1,3 +1,5 @@
@import "../../libraries/subtitles";
.player .player
{ {
position: fixed; position: fixed;
@ -15,6 +17,20 @@
} }
} }
#hover
{
transition: opacity .2s linear;
opacity: 1;
visibility: visible;
&.idle
{
transition: opacity .6s linear, visibility 0s .6s;
opacity: 0;
visibility: hidden;
}
}
.back .back
{ {
position: fixed; position: fixed;
@ -267,7 +283,7 @@
overflow: hidden; overflow: hidden;
transition: width .2s cubic-bezier(0.4,0, 1, 1); transition: width .2s cubic-bezier(0.4,0, 1, 1);
&::ng-deep > div > div
{ {
top: 19px; top: 19px;
left: 10px; left: 10px;

View File

@ -1,15 +1,17 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { WatchItem } from "../../models/watch-item"; import { WatchItem } 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";
import { MatSliderChange } from "@angular/material/slider"; import { MatSliderChange } from "@angular/material/slider";
import { HtmlAstPath } from "@angular/compiler";
declare var SubtitleManager: any;
@Component({ @Component({
selector: 'app-player', selector: 'app-player',
templateUrl: './player.component.html', templateUrl: './player.component.html',
styleUrls: ['./player.component.scss'] styleUrls: ['./player.component.scss'],
encapsulation: ViewEncapsulation.None
}) })
export class PlayerComponent implements OnInit export class PlayerComponent implements OnInit
{ {
@ -17,10 +19,11 @@ export class PlayerComponent implements OnInit
volume: number = 100; volume: number = 100;
seeking: boolean = false; seeking: boolean = false;
videoHider;
hours: number; hours: number;
minutes: number; minutes: number = 0;
seconds: number; seconds: number = 0;
maxHours: number; maxHours: number;
maxMinutes: number; maxMinutes: number;
@ -51,9 +54,7 @@ export class PlayerComponent implements OnInit
this.initPlayBtn(); this.initPlayBtn();
} }
this.maxSeconds = Math.round(this.item.duration % 60); this.setDuration(this.item.duration);
this.maxMinutes = Math.round(this.item.duration / 60 % 60);
this.maxHours = Math.round(this.item.duration / 3600);
this.title.setTitle(this.item.showTitle + " S" + this.item.seasonNumber + ":E" + this.item.episodeNumber + " - Kyoo"); this.title.setTitle(this.item.showTitle + " S" + this.item.seasonNumber + ":E" + this.item.episodeNumber + " - Kyoo");
}); });
@ -91,11 +92,15 @@ export class PlayerComponent implements OnInit
{ {
if (this.player.buffered.length > 0) if (this.player.buffered.length > 0)
this.buffered.style.width = (this.player.buffered.end(this.player.buffered.length - 1) / this.item.duration * 100) + "%"; this.buffered.style.width = (this.player.buffered.end(this.player.buffered.length - 1) / this.item.duration * 100) + "%";
if (this.player.duration != undefined && this.player.duration != Infinity)
this.setDuration(this.player.duration);
}; };
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;
@ -134,8 +139,34 @@ export class PlayerComponent implements OnInit
let time: number = this.getTimeFromSeekbar(progressBar, event.pageX); let time: number = this.getTimeFromSeekbar(progressBar, event.pageX);
this.updateTime(time); this.updateTime(time);
} }
else
{
document.getElementById("hover").classList.remove("idle");
document.documentElement.style.cursor = "";
clearTimeout(this.videoHider);
this.videoHider = setTimeout(() =>
{
if (!this.player.paused)
{
document.getElementById("hover").classList.add("idle");
document.documentElement.style.cursor = "none";
}
}, 2000);
}
}); });
//Initialize the timout at the document initialization.
this.videoHider = setTimeout(() =>
{
if (!this.player.paused)
{
document.getElementById("hover").classList.add("idle");
document.documentElement.style.cursor = "none";
}
}, 2000);
document.addEventListener("fullscreenchange", () => document.addEventListener("fullscreenchange", () =>
{ {
if (document.fullscreenElement != null) if (document.fullscreenElement != null)
@ -151,6 +182,8 @@ 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)
@ -158,6 +191,15 @@ export class PlayerComponent implements OnInit
return Math.max(0, Math.min((pageX - progressBar.offsetLeft) / progressBar.clientWidth, 1)) * this.item.duration; return Math.max(0, Math.min((pageX - progressBar.offsetLeft) / progressBar.clientWidth, 1)) * this.item.duration;
} }
setDuration(duration: number)
{
this.maxSeconds = Math.round(duration % 60);
this.maxMinutes = Math.round(duration / 60 % 60);
this.maxHours = Math.round(duration / 3600);
this.item.duration = duration;
}
updateTime(time: number) updateTime(time: number)
{ {
this.hours = Math.round(time / 60 % 60); this.hours = Math.round(time / 60 % 60);

View File

@ -0,0 +1,23 @@
.subtitle_container {
line-height: normal;
position: absolute;
pointer-events: none;
transform-origin: 0 0 0;
top: 0;
left: 0;
}
.subtitle_container text, .subtitle_container path {
dominant-baseline: text-before-edge;
text-anchor: start;
transform-box: view-box;
paint-order: stroke;
position: absolute;
top: 0;
left: 0;
}
.subtitle_container tspan {
white-space: pre;
}
.subtitle_container mask path {
fill: white;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
using Kyoo.InternalAPI;
using Microsoft.AspNetCore.Mvc;
namespace Kyoo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SubtitleController : ControllerBase
{
private readonly ILibraryManager libraryManager;
public SubtitleController(ILibraryManager libraryManager)
{
this.libraryManager = libraryManager;
}
[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}-{languageTag}.ass")]
public IActionResult GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag)
{
return PhysicalFile(@"D:\Videos\Devilman\Subtitles\fre\Devilman Crybaby S01E01.fre.ass", "text/x-ssa");
}
}
}

View File

@ -26,7 +26,7 @@ namespace Kyoo.Controllers
{ {
//Should check if video is playable on the client and transcode if needed. //Should check if video is playable on the client and transcode if needed.
//Should use the right mime type //Should use the right mime type
return new PhysicalFileResult(episode.Path, "video/mp4"); return PhysicalFile(episode.Path, "video/mp4", true);
} }
else else
return NotFound(); return NotFound();

View File

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.AngularCli; using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Web.Http; using System.Web.Http;