mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Creating subtitle support on the web-app.
This commit is contained in:
parent
9527894fad
commit
e5d3ea1257
@ -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": {
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
23
Kyoo/ClientApp/src/libraries/subtitles.css
Normal file
23
Kyoo/ClientApp/src/libraries/subtitles.css
Normal 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;
|
||||||
|
}
|
3372
Kyoo/ClientApp/src/libraries/subtitles.js
Normal file
3372
Kyoo/ClientApp/src/libraries/subtitles.js
Normal file
File diff suppressed because it is too large
Load Diff
23
Kyoo/Controllers/SubtitleController.cs
Normal file
23
Kyoo/Controllers/SubtitleController.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user