mirror of
https://codeberg.org/ashley/poke.git
synced 2024-12-26 20:18:46 +01:00
325 lines
11 KiB
Text
325 lines
11 KiB
Text
@using System.Text.RegularExpressions
|
|
@using System.Web
|
|
@using InnerTube.Models
|
|
@model LightTube.Contexts.PlayerContext
|
|
|
|
@{
|
|
bool compatibility = false;
|
|
if (Context.Request.Cookies.TryGetValue("compatibility", out string compatibilityString))
|
|
bool.TryParse(compatibilityString, out compatibility);
|
|
|
|
ViewBag.Metadata = new Dictionary<string, string>();
|
|
ViewBag.Metadata["author"] = Model.Video.Channel.Name;
|
|
ViewBag.Metadata["og:title"] = Model.Player.Title;
|
|
ViewBag.Metadata["og:url"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}{Url.ActionContext.HttpContext.Request.Path}{Url.ActionContext.HttpContext.Request.QueryString}";
|
|
ViewBag.Metadata["og:image"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.FirstOrDefault()?.Url?.ToString())}";
|
|
ViewBag.Metadata["twitter:card"] = $"{Url.ActionContext.HttpContext.Request.Scheme}://{Url.ActionContext.HttpContext.Request.Host}/proxy/image?url={HttpUtility.UrlEncode(Model.Player.Thumbnails.LastOrDefault()?.Url?.ToString())}";
|
|
ViewBag.Metadata["og:description"] = Model.Player.Description;
|
|
ViewBag.Title = Model.Player.Title;
|
|
|
|
Layout = "_Layout";
|
|
try
|
|
{
|
|
ViewBag.Metadata["og:video"] = $"/proxy/video?url={HttpUtility.UrlEncode(Model.Player.Formats.First().Url.ToString())}";
|
|
Model.Resolution ??= Model.Player.Formats.First().FormatNote;
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
ViewData["HideGuide"] = true;
|
|
|
|
bool live = Model.Player.Formats.Length == 0 && Model.Player.AdaptiveFormats.Length > 0;
|
|
string description = Model.Video.GetHtmlDescription();
|
|
const string youtubePattern = @"[w.]*youtube[-nockie]*\.com";
|
|
|
|
// turn URLs into hyperlinks
|
|
Regex urlRegex = new(youtubePattern, RegexOptions.IgnoreCase);
|
|
Match m;
|
|
for (m = urlRegex.Match(description); m.Success; m = m.NextMatch())
|
|
description = description.Replace(m.Groups[0].ToString(),
|
|
$"{Url.ActionContext.HttpContext.Request.Host}");
|
|
|
|
bool canPlay = true;
|
|
}
|
|
|
|
<!-- TODO: chapters -->
|
|
<div class="watch-page">
|
|
<div class="primary">
|
|
<div class="video-player-container">
|
|
|
|
@if (live)
|
|
{
|
|
<video class="player" poster="@Model.Player.Thumbnails.LastOrDefault()?.Url">
|
|
</video>
|
|
}
|
|
else if (Model.Player.Formats.Length > 0)
|
|
{
|
|
<video class="player" controls src="/proxy/media/@Model.Player.Id/@HttpUtility.UrlEncode(Model.Player.Formats.First(x => x.FormatNote == Model.Resolution && x.FormatId != "17").FormatId)" poster="@Model.Player.Thumbnails.LastOrDefault()?.Url">
|
|
@foreach (Subtitle subtitle in Model.Player.Subtitles ?? Array.Empty<Subtitle>())
|
|
{
|
|
@:<track src="/proxy/caption/@Model.Player.Id/@HttpUtility.UrlEncode(subtitle.Language).Replace("+", "%20")" label="@subtitle.Language" kind="subtitles">
|
|
}
|
|
</video>
|
|
}
|
|
else
|
|
{
|
|
canPlay = false;
|
|
<div id="player" class="player error" style="background-image: url('@Model.Player.Thumbnails.LastOrDefault()?.Url')">
|
|
@if (string.IsNullOrWhiteSpace(Model.Player.ErrorMessage))
|
|
{
|
|
<span>
|
|
No playable streams returned from the API (@Model.Player.Formats.Length/@Model.Player.AdaptiveFormats.Length)
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span>
|
|
@Model.Player.ErrorMessage
|
|
</span>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
@if (Model.MobileLayout)
|
|
{
|
|
<div class="video-info">
|
|
<div class="video-title">@Model.Video.Title</div>
|
|
<div class="video-info-bar">
|
|
<span>@Model.Video.Views</span>
|
|
<span>Published @Model.Video.UploadDate</span>
|
|
<div class="divider"></div>
|
|
<div class="video-info-buttons">
|
|
<div>
|
|
<i class="bi bi-hand-thumbs-up"></i><span>@Model.Engagement.Likes</span>
|
|
</div>
|
|
<div>
|
|
<i class="bi bi-hand-thumbs-down"></i><span>@Model.Engagement.Dislikes</span>
|
|
</div>
|
|
<a href="/download?v=@Model.Video.Id">
|
|
<i class="bi bi-download"></i>
|
|
Download
|
|
</a>
|
|
<a href="/Account/AddVideoToPlaylist?v=@Model.Video.Id">
|
|
<i class="bi bi-folder-plus"></i>
|
|
Save
|
|
</a>
|
|
<a href="https://www.youtube.com/watch?v=@Model.Video.Id">
|
|
<i class="bi bi-share"></i>
|
|
YouTube link
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="channel-info">
|
|
<a href="/channel/@Model.Video.Channel.Id" class="avatar">
|
|
<img src="@Model.Video.Channel.Avatars.LastOrDefault()?.Url">
|
|
</a>
|
|
<div class="name">
|
|
<a href="/channel/@Model.Video.Channel.Id">@Model.Video.Channel.Name</a>
|
|
</div>
|
|
<button class="subscribe-button" data-cid="@Model.Video.Channel.Id">Subscribe</button>
|
|
</div>
|
|
<p class="description">@Html.Raw(description)</p>
|
|
</div>
|
|
<hr>
|
|
}
|
|
else
|
|
{
|
|
<div class="video-info">
|
|
<div class="video-title">@Model.Video.Title</div>
|
|
<p class="video-sub-info description">
|
|
<span>@Model.Video.Views @Model.Video.UploadDate</span> @Html.Raw(description)
|
|
</p>
|
|
<div class="video-info-buttons">
|
|
<div>
|
|
<i class="bi bi-hand-thumbs-up"></i>
|
|
@Model.Engagement.Likes
|
|
</div>
|
|
<div>
|
|
<i class="bi bi-hand-thumbs-down"></i>
|
|
@Model.Engagement.Dislikes
|
|
</div>
|
|
<a href="/download?v=@Model.Player.Id">
|
|
<i class="bi bi-download"></i>
|
|
Download
|
|
</a>
|
|
<a href="/Account/AddVideoToPlaylist?v=@Model.Video.Id">
|
|
<i class="bi bi-folder-plus"></i>
|
|
Save
|
|
</a>
|
|
<a href="https://www.youtube.com/watch?v=@Model.Video.Id">
|
|
<i class="bi bi-share"></i>
|
|
YouTube link
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="channel-info__bordered">
|
|
<a href="/channel/@Model.Video.Channel.Id" class="avatar">
|
|
<img src="@Model.Video.Channel.Avatars.FirstOrDefault()?.Url">
|
|
</a>
|
|
<div class="name">
|
|
<a href="/channel/@Model.Video.Channel.Id">@Model.Video.Channel.Name</a>
|
|
</div>
|
|
<div class="subscriber-count">
|
|
@Model.Video.Channel.SubscriberCount
|
|
</div>
|
|
<button class="subscribe-button" data-cid="@Model.Video.Channel.Id">Subscribe</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
<div class="secondary">
|
|
<noscript>
|
|
<div class="resolutions-list">
|
|
<h3>Change Resolution</h3>
|
|
<div>
|
|
@foreach (Format format in Model.Player.Formats.Where(x => x.FormatId != "17"))
|
|
{
|
|
@if (format.FormatNote == Model.Resolution)
|
|
{
|
|
<b>@format.FormatNote (current)</b>
|
|
}
|
|
else
|
|
{
|
|
<a href="/watch?v=@Model.Player.Id&quality=@format.FormatNote">@format.FormatNote</a>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
</noscript>
|
|
<div class="recommended-list">
|
|
|
|
@if (Model.Video.Recommended.Length == 0)
|
|
{
|
|
<p style="text-align: center">None :(<br>This is most likely an age-restricted video</p>
|
|
}
|
|
@foreach (DynamicItem recommendation in Model.Video.Recommended)
|
|
{
|
|
switch (recommendation)
|
|
{
|
|
case VideoItem video:
|
|
<div class="video">
|
|
<a href="/watch?v=@video.Id" class="thumbnail" style="background-image: url('@video.Thumbnails.LastOrDefault()?.Url')">
|
|
<span class="video-length">@video.Duration</span>
|
|
</a>
|
|
<div class="info">
|
|
<a href="/watch?v=@video.Id" class="title max-lines-2">@video.Title</a>
|
|
<div>
|
|
<a href="/channel/@video.Channel.Id" class="max-lines-1">@video.Channel.Name</a>
|
|
<div>
|
|
<span>@video.Views views</span>
|
|
<span>•</span>
|
|
<span>@video.UploadedAt</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
break;
|
|
case PlaylistItem playlist:
|
|
<div class="playlist">
|
|
<a href="/watch?v=@playlist.FirstVideoId&list=@playlist.Id" class="thumbnail" style="background-image: url('@playlist.Thumbnails.LastOrDefault()?.Url')">
|
|
<div>
|
|
<span>@playlist.VideoCount</span>
|
|
<span>VIDEOS</span>
|
|
</div>
|
|
</a>
|
|
<div class="info">
|
|
<a href="/watch?v=@playlist.FirstVideoId&list=@playlist.Id" class="title max-lines-2">@playlist.Title</a>
|
|
<div>
|
|
<a href="/channel/@playlist.Channel.Id">@playlist.Channel.Name</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
break;
|
|
case RadioItem radio:
|
|
<div class="playlist">
|
|
<a href="/watch?v=@radio.FirstVideoId&list=@radio.Id" class="thumbnail" style="background-image: url('@radio.Thumbnails.LastOrDefault()?.Url')">
|
|
<div>
|
|
<span>MIX</span>
|
|
</div>
|
|
</a>
|
|
<div class="info">
|
|
<a href="/watch?v=@radio.FirstVideoId&list=@radio.Id" class="title max-lines-2">@radio.Title</a>
|
|
<div>
|
|
<span>@radio.Channel.Name</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
break;
|
|
case ContinuationItem continuationItem:
|
|
break;
|
|
default:
|
|
<div class="video">
|
|
<div class="thumbnail" style="background-image: url('@recommendation.Thumbnails?.LastOrDefault()?.Url')"></div>
|
|
<div class="info">
|
|
<span class="title max-lines-2">@recommendation.GetType().Name</span>
|
|
<div>
|
|
<b>WARNING:</b> Unknown recommendation type: @recommendation.Id
|
|
</div>
|
|
</div>
|
|
</div>
|
|
break;
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (canPlay)
|
|
{
|
|
@if (Model.MobileLayout)
|
|
{
|
|
<script src="/js/lt-video/player-mobile.js"></script>
|
|
}
|
|
else
|
|
{
|
|
<script src="/js/lt-video/player-desktop.js"></script>
|
|
}
|
|
@if (!Model.CompatibilityMode && !live)
|
|
{
|
|
<script src="/js/shaka-player/shaka-player.compiled.min.js"></script>
|
|
<script>
|
|
let player = undefined;
|
|
loadPlayerWithShaka("video", {
|
|
"id": "@Model.Video.Id",
|
|
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
|
|
"embed": false,
|
|
"live": false,
|
|
"storyboard": "/proxy/image?url=@HttpUtility.UrlEncode(Model.Player.Storyboards.FirstOrDefault())"
|
|
}, [
|
|
@foreach (Format f in Model.Player.Formats.Reverse())
|
|
{
|
|
@:{"height": @f.Resolution.Split("x")[1],"label":"@f.FormatName","src": "/proxy/video?url=@HttpUtility.UrlEncode(f.Url)"},
|
|
}
|
|
], "https://@(Context.Request.Host)/manifest/@(Model.Video.Id).mpd").then(x => player = x).catch(alert);
|
|
</script>
|
|
}
|
|
else if (live)
|
|
{
|
|
<script src="/js/hls.js/hls.min.js"></script>
|
|
<script>
|
|
let player = undefined;
|
|
loadPlayerWithHls("video", {
|
|
"id": "@(Model.Video.Id)",
|
|
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
|
|
"embed": false,
|
|
"live": true
|
|
}, "https://@(Context.Request.Host)/manifest/@(Model.Video.Id).m3u8").then(x => player = x).catch(alert);
|
|
</script>
|
|
}
|
|
else
|
|
{
|
|
<script>
|
|
const player = new Player("video", {
|
|
"id": "@Model.Video.Id",
|
|
"title": "@Html.Raw(Model.Video.Title.Replace("\"", "\\\""))",
|
|
"embed": false,
|
|
"live": false,
|
|
"storyboard": "/proxy/image?url=@HttpUtility.UrlEncode(Model.Player.Storyboards.FirstOrDefault())"
|
|
}, [
|
|
@foreach (Format f in Model.Player.Formats.Reverse())
|
|
{
|
|
@:{"height": @f.Resolution.Split("x")[1],"label":"@f.FormatName","src": "/proxy/media/@(Model.Player.Id)/@(f.FormatId)"},
|
|
}
|
|
]);
|
|
</script>
|
|
}
|
|
}
|