mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Creating the web app player view.
This commit is contained in:
parent
2462d3ad7f
commit
21002fea4a
@ -14,7 +14,7 @@ const routes: Routes = [
|
|||||||
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
|
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
|
||||||
{ path: "browse/:library-slug", component: BrowseComponent, resolve: { shows: LibraryResolverService } },
|
{ path: "browse/:library-slug", component: BrowseComponent, resolve: { shows: LibraryResolverService } },
|
||||||
{ path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
|
{ path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
|
||||||
{ path: "watch/:item", component: PlayerComponent, resolve: { show: StreamResolverService } },
|
{ path: "watch/:item", component: PlayerComponent, resolve: { item: StreamResolverService }, runGuardsAndResolvers: "always" },
|
||||||
{ path: "**", component: NotFoundComponent }
|
{ path: "**", component: NotFoundComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1 +1,58 @@
|
|||||||
<p>player works!</p>
|
<div id="root">
|
||||||
|
<div class="player">
|
||||||
|
<video id="player" autoplay muted (click)="tooglePlayback()">
|
||||||
|
<source src="/api/video/{{this.video}}" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="back">
|
||||||
|
<button mat-icon-button data-toggle="tooltip" data-placement="bottom" title="Back" (click)="back()">
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</button>
|
||||||
|
<h5>{{this.item.showTitle}}</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controller container-fluid">
|
||||||
|
<div class="img">
|
||||||
|
<img src="thumb/{{this.item.showSlug}}" />
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h3>S{{this.item.seasonNumber}}:E{{this.item.episodeNumber}} - {{this.item.title}}</h3>
|
||||||
|
<mat-progress-bar color="accent"></mat-progress-bar>
|
||||||
|
<div class="buttons">
|
||||||
|
<div class="left">
|
||||||
|
<button *ngIf="this.item.episodeNumber != 1" mat-icon-button data-toggle="tooltip" data-placement="top" title="Previous" routerLink="/watch/{{this.item.showSlug}}-s{{this.item.seasonNumber}}e{{this.item.episodeNumber - 1}}">
|
||||||
|
<mat-icon>skip_previous</mat-icon>
|
||||||
|
</button>
|
||||||
|
<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 data-toggle="tooltip" data-placement="top" title="Next" routerLink="/watch/{{this.item.showSlug}}-s{{this.item.seasonNumber}}e{{this.item.episodeNumber + 1}}">
|
||||||
|
<mat-icon>skip_next</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Volume">
|
||||||
|
<mat-icon>volume_up</mat-icon>
|
||||||
|
</button>
|
||||||
|
<p>00:00 / --:--</p>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<button *ngIf="this.item.audios != null" mat-icon-button data-toggle="tooltip" data-placement="top" title="Select audio track">
|
||||||
|
<mat-icon>music_note</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="this.item.subtitles != null" mat-icon-button data-toggle="tooltip" data-placement="top" title="Select subtitle track">
|
||||||
|
<mat-icon>closed_caption</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Cast">
|
||||||
|
<mat-icon>cast</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Settings">
|
||||||
|
<mat-icon>settings</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Fullscreen" id="fullscreen" (click)="fullscreen()">
|
||||||
|
<mat-icon>{{fullscreenIcon}}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
.player
|
||||||
|
{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #000;
|
||||||
|
|
||||||
|
> video
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.back
|
||||||
|
{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: .33%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> button
|
||||||
|
{
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> h5
|
||||||
|
{
|
||||||
|
margin: 0;
|
||||||
|
margin-left: .5rem;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controller
|
||||||
|
{
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
padding: 1%;
|
||||||
|
|
||||||
|
.img
|
||||||
|
{
|
||||||
|
width: 15%;
|
||||||
|
position: relative;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
> img
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> mat-progress-bar
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> div
|
||||||
|
{
|
||||||
|
&.left
|
||||||
|
{
|
||||||
|
align-self: start;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> p
|
||||||
|
{
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 1rem;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right
|
||||||
|
{
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button
|
||||||
|
{
|
||||||
|
margin-left: .3rem;
|
||||||
|
margin-right: .3rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,93 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { WatchItem } from "../../models/watch-item";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
|
import { Location } from "@angular/common";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-player',
|
selector: 'app-player',
|
||||||
templateUrl: './player.component.html',
|
templateUrl: './player.component.html',
|
||||||
styleUrls: ['./player.component.scss']
|
styleUrls: ['./player.component.scss']
|
||||||
})
|
})
|
||||||
export class PlayerComponent implements OnInit {
|
export class PlayerComponent implements OnInit
|
||||||
|
{
|
||||||
|
item: WatchItem;
|
||||||
|
video: string;
|
||||||
|
|
||||||
constructor() { }
|
playIcon: string = "pause"; //Icon used by the play btn.
|
||||||
|
fullscreenIcon: string = "fullscreen"; //Icon used by the fullscreen btn.
|
||||||
|
|
||||||
|
private player: HTMLVideoElement;
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute, private location: Location)
|
||||||
|
{
|
||||||
|
this.video = this.route.snapshot.paramMap.get("item");
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit()
|
ngOnInit()
|
||||||
{
|
{
|
||||||
document.getElementById("nav").classList.add("d-none");
|
document.getElementById("nav").classList.add("d-none");
|
||||||
|
this.item = this.route.snapshot.data.item;
|
||||||
|
console.log("Init");
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit()
|
||||||
|
{
|
||||||
|
this.player = document.getElementById("player") as HTMLVideoElement;
|
||||||
|
this.player.controls = false;
|
||||||
|
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
|
||||||
|
document.addEventListener("fullscreenchange", (event) =>
|
||||||
|
{
|
||||||
|
if (document.fullscreenElement != null)
|
||||||
|
{
|
||||||
|
this.fullscreenIcon = "fullscreen_exit";
|
||||||
|
$("#fullscreen").attr("data-original-title", "Exit fullscreen").tooltip("show");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.fullscreenIcon = "fullscreen";
|
||||||
|
$("#fullscreen").attr("data-original-title", "Fullscreen").tooltip("show");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy()
|
ngOnDestroy()
|
||||||
{
|
{
|
||||||
document.getElementById("nav").classList.remove("d-none");
|
document.getElementById("nav").classList.remove("d-none");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
back()
|
||||||
|
{
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
tooglePlayback()
|
||||||
|
{
|
||||||
|
let playBtn: HTMLElement = document.getElementById("play");
|
||||||
|
|
||||||
|
if (this.player.paused)
|
||||||
|
{
|
||||||
|
this.player.play();
|
||||||
|
|
||||||
|
this.playIcon = "pause"
|
||||||
|
$(playBtn).attr("data-original-title", "Pause").tooltip("show");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.player.pause();
|
||||||
|
|
||||||
|
this.playIcon = "play_arrow"
|
||||||
|
$(playBtn).attr("data-original-title", "Play").tooltip("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fullscreen()
|
||||||
|
{
|
||||||
|
if (document.fullscreenElement == null)
|
||||||
|
document.getElementById("root").requestFullscreen();
|
||||||
|
else
|
||||||
|
document.exitFullscreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
3
Kyoo/ClientApp/src/models/watch-item.js
Normal file
3
Kyoo/ClientApp/src/models/watch-item.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=watch-item.js.map
|
1
Kyoo/ClientApp/src/models/watch-item.js.map
Normal file
1
Kyoo/ClientApp/src/models/watch-item.js.map
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"watch-item.js","sourceRoot":"","sources":["watch-item.ts"],"names":[],"mappings":""}
|
20
Kyoo/ClientApp/src/models/watch-item.ts
Normal file
20
Kyoo/ClientApp/src/models/watch-item.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export interface WatchItem
|
||||||
|
{
|
||||||
|
showTitle: string;
|
||||||
|
showSlug: string;
|
||||||
|
seasonNumber: number;
|
||||||
|
episodeNumber: number;
|
||||||
|
title: string;
|
||||||
|
releaseDate;
|
||||||
|
audio: Stream[];
|
||||||
|
subtitles: Stream[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stream
|
||||||
|
{
|
||||||
|
title: string;
|
||||||
|
language: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
isForced: boolean;
|
||||||
|
format: string;
|
||||||
|
}
|
@ -305,7 +305,7 @@ namespace Kyoo.InternalAPI
|
|||||||
|
|
||||||
public WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber)
|
public WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber)
|
||||||
{
|
{
|
||||||
string query = "SELECT episodes.id, shows.title as showTitle, seasonNumber, episodeNumber, episodes.title, releaseDate, episodes.path FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber;";
|
string query = "SELECT episodes.id, shows.title as showTitle, shows.slug as showSlug, seasonNumber, episodeNumber, episodes.title, releaseDate, episodes.path FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber;";
|
||||||
|
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
{
|
{
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="ClientApp\src\models\show.ts" />
|
<None Remove="ClientApp\src\models\show.ts" />
|
||||||
|
<None Remove="ClientApp\src\models\watch-item.ts" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -69,6 +70,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<TypeScriptCompile Include="ClientApp\src\models\show.ts" />
|
<TypeScriptCompile Include="ClientApp\src\models\show.ts" />
|
||||||
|
<TypeScriptCompile Include="ClientApp\src\models\watch-item.ts" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
||||||
|
@ -11,6 +11,7 @@ namespace Kyoo.Models
|
|||||||
[JsonIgnore] public readonly long episodeID;
|
[JsonIgnore] public readonly long episodeID;
|
||||||
|
|
||||||
public string ShowTitle;
|
public string ShowTitle;
|
||||||
|
public string ShowSlug;
|
||||||
public long seasonNumber;
|
public long seasonNumber;
|
||||||
public long episodeNumber;
|
public long episodeNumber;
|
||||||
public string Title;
|
public string Title;
|
||||||
@ -23,10 +24,11 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public WatchItem() { }
|
public WatchItem() { }
|
||||||
|
|
||||||
public WatchItem(long episodeID, string showTitle, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path)
|
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path)
|
||||||
{
|
{
|
||||||
this.episodeID = episodeID;
|
this.episodeID = episodeID;
|
||||||
ShowTitle = showTitle;
|
ShowTitle = showTitle;
|
||||||
|
ShowSlug = showSlug;
|
||||||
this.seasonNumber = seasonNumber;
|
this.seasonNumber = seasonNumber;
|
||||||
this.episodeNumber = episodeNumber;
|
this.episodeNumber = episodeNumber;
|
||||||
Title = title;
|
Title = title;
|
||||||
@ -34,7 +36,7 @@ namespace Kyoo.Models
|
|||||||
Path = path;
|
Path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WatchItem(long episodeID, string showTitle, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Stream[] audios, Stream[] subtitles) : this(episodeID, showTitle, seasonNumber, episodeNumber, title, releaseDate, path)
|
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Stream[] audios, Stream[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path)
|
||||||
{
|
{
|
||||||
this.audios = audios;
|
this.audios = audios;
|
||||||
this.subtitles = subtitles;
|
this.subtitles = subtitles;
|
||||||
@ -44,6 +46,7 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
return new WatchItem((long)reader["id"],
|
return new WatchItem((long)reader["id"],
|
||||||
reader["showTitle"] as string,
|
reader["showTitle"] as string,
|
||||||
|
reader["showSlug"] as string,
|
||||||
(long)reader["seasonNumber"],
|
(long)reader["seasonNumber"],
|
||||||
(long)reader["episodeNumber"],
|
(long)reader["episodeNumber"],
|
||||||
reader["title"] as string,
|
reader["title"] as string,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user