diff --git a/src/invidious.cr b/src/invidious.cr index 34900d6d..b5fea229 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -373,6 +373,79 @@ get "/watch" do |env| templated "watch" end +get "/embed/:id" do |env| + if env.params.url["id"]? + id = env.params.url["id"] + else + next env.redirect "/" + end + + if env.params.query["start"]? + video_start = decode_time(env.params.query["start"]) + end + + if env.params.query["t"]? + video_start = decode_time(env.params.query["t"]) + end + video_start ||= 0 + + if env.params.query["end"]? + video_end = decode_time(env.params.query["end"]) + end + video_end ||= -1 + + if env.params.query["listen"]? && env.params.query["listen"] == "true" + listen = true + env.params.query.delete_all("listen") + end + listen ||= false + + client = make_client(YT_URL) + begin + video = get_video(id, client, PG_DB) + rescue ex + error_message = ex.message + next templated "error" + end + + fmt_stream = [] of HTTP::Params + video.info["url_encoded_fmt_stream_map"].split(",") do |string| + if !string.empty? + fmt_stream << HTTP::Params.parse(string) + end + end + + fmt_stream.each { |s| s.add("label", "#{s["quality"]} - #{s["type"].split(";")[0].split("/")[1]}") } + fmt_stream = fmt_stream.uniq { |s| s["label"] } + + adaptive_fmts = [] of HTTP::Params + if video.info.has_key?("adaptive_fmts") + video.info["adaptive_fmts"].split(",") do |string| + adaptive_fmts << HTTP::Params.parse(string) + end + end + + if adaptive_fmts[0]? && adaptive_fmts[0]["s"]? + adaptive_fmts.each do |fmt| + fmt["url"] += "&signature=" + decrypt_signature(fmt["s"]) + end + + fmt_stream.each do |fmt| + fmt["url"] += "&signature=" + decrypt_signature(fmt["s"]) + end + end + + audio_streams = adaptive_fmts.compact_map { |s| s["type"].starts_with?("audio") ? s : nil } + audio_streams.sort_by! { |s| s["bitrate"].to_i }.reverse! + audio_streams.each do |stream| + stream["bitrate"] = (stream["bitrate"].to_f64/1000).to_i.to_s + end + + thumbnail = "https://i.ytimg.com/vi/#{id}/mqdefault.jpg" + + rendered "embed" +end + get "/search" do |env| if env.params.query["q"]? query = env.params.query["q"] diff --git a/src/invidious/views/embed.ecr b/src/invidious/views/embed.ecr new file mode 100644 index 00000000..38eb5155 --- /dev/null +++ b/src/invidious/views/embed.ecr @@ -0,0 +1,143 @@ +<!DOCTYPE html> +<html> + +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<meta name="thumbnail" content="<%= thumbnail %>"> +<link rel="stylesheet" href="https://unpkg.com/video.js@6.10.3/dist/video-js.min.css"> +<link rel="stylesheet" href="https://unpkg.com/silvermine-videojs-quality-selector@1.1.2/dist/css/quality-selector.css"> +<script src="https://unpkg.com/video.js@6.10.3/dist/video.min.js"></script> +<script src="https://unpkg.com/videojs-hotkeys@0.2.21/videojs.hotkeys.min.js"></script> +<script src="https://unpkg.com/silvermine-videojs-quality-selector@1.1.2/dist/js/silvermine-videojs-quality-selector.min.js"></script> +<script src="https://unpkg.com/videojs-offset@2.0.0-beta.2/dist/videojs-offset.min.js"></script> +<title><%= video.title %> - Invidious</title> +</head> + +<body> +<style> +video, #my_video, .video-js, .vjs-default-skin +{ + position: fixed; + right: 0; + bottom: 0; + min-width: 100%; + min-height: 100%; + width: auto; + height: auto; + z-index: -100; +} +</style> + +<video playsinline poster="<%= thumbnail %>" title="<%= HTML.escape(video.title) %>" id="player" class="video-js vjs-default-skin" controls> + <% if listen %> + <% audio_streams.each_with_index do |fmt, i| %> + <source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["bitrate"] %>k" selected="<%= i == 0 ? true : false %>"> + <% end %> + <% else %> + <% fmt_stream.each_with_index do |fmt, i| %> + <source src="<%= fmt["url"] %>" type='<%= fmt["type"] %>' label="<%= fmt["label"] %>" selected="<%= i == 0 ? true : false %>"> + <% end %> + <% end %> +</video> + +<script> +var options = { + preload: "auto", + playbackRates: [0.5, 1, 1.5, 2], + controlBar: { + children: [ + 'playToggle', + 'volumePanel', + 'progressControl', + 'remainingTimeDisplay', + 'qualitySelector', + 'playbackRateMenuButton', + 'fullscreenToggle', + ], + }, +}; +var player = videojs('player', options, function() { + this.hotkeys({ + volumeStep: 0.1, + seekStep: 5, + enableModifiersForNumbers: false, + enableVolumeScroll: false, + customKeys: { + play: { + key: function(e) { + // Toggle play with K Key + return (e.which === 75); + }, + handler: function(player, options, e) { + if (player.paused()) { + player.play(); + } else { + player.pause(); + } + } + }, + backward: { + key: function(e) { + // Go backward 5 seconds + return (e.which === 74); + }, + handler: function(player, options, e) { + player.currentTime(player.currentTime() - 5); + } + }, + forward: { + key: function(e) { + // Go forward 5 seconds + return (e.which === 76); + }, + handler: function(player, options, e) { + player.currentTime(player.currentTime() + 5); + } + } + } + }); +}); + +player.offset({ + start: <%= video_start %>, + end: <%= video_end %> +}); + +function toggle(target) { + body = target.parentNode.parentNode.children[1]; + if (body.style.display === null || body.style.display === '') { + target.innerHTML = '[ + ]'; + body.style.display = 'none'; + } else { + target.innerHTML = '[ - ]'; + body.style.display = ''; + } +}; + +function toggle_comments(target) { + body = target.parentNode.parentNode.parentNode.children[1]; + if (body.style.display === null || body.style.display === '') { + target.innerHTML = '[ + ]'; + body.style.display = 'none'; + } else { + target.innerHTML = '[ - ]'; + body.style.display = ''; + } +}; + +<% if !listen %> +var currentSources = player.currentSources(); +for ( var i = 0; i < currentSources.length; i++ ) { + if (player.canPlayType(currentSources[i]["type"].split(";")[0]) === "") { + currentSources.splice(i); + i--; + } +} + +player.src(currentSources); +<% end %> +</script> +</body> + +</html> \ No newline at end of file