mirror of
https://github.com/iv-org/invidious.git
synced 2024-11-25 15:07:22 +00:00
Add playlist playback support
This commit is contained in:
parent
c72b9bea64
commit
88430a6fc0
@ -17,6 +17,11 @@ div {
|
|||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playlist-restricted {
|
||||||
|
height: 20em;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Navbar
|
* Navbar
|
||||||
*/
|
*/
|
||||||
|
@ -217,6 +217,8 @@ get "/watch" do |env|
|
|||||||
next env.redirect "/"
|
next env.redirect "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
plid = env.params.query["list"]?
|
||||||
|
|
||||||
user = env.get? "user"
|
user = env.get? "user"
|
||||||
if user
|
if user
|
||||||
user = user.as(User)
|
user = user.as(User)
|
||||||
@ -2939,6 +2941,11 @@ get "/api/v1/playlists/:plid" do |env|
|
|||||||
page = env.params.query["page"]?.try &.to_i?
|
page = env.params.query["page"]?.try &.to_i?
|
||||||
page ||= 1
|
page ||= 1
|
||||||
|
|
||||||
|
format = env.params.query["format"]?
|
||||||
|
format ||= "json"
|
||||||
|
|
||||||
|
continuation = env.params.query["continuation"]?
|
||||||
|
|
||||||
if plid.starts_with? "RD"
|
if plid.starts_with? "RD"
|
||||||
next env.redirect "/api/v1/mixes/#{plid}"
|
next env.redirect "/api/v1/mixes/#{plid}"
|
||||||
end
|
end
|
||||||
@ -2951,7 +2958,7 @@ get "/api/v1/playlists/:plid" do |env|
|
|||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
videos = fetch_playlist_videos(plid, page, playlist.video_count)
|
videos = fetch_playlist_videos(plid, page, playlist.video_count, continuation)
|
||||||
rescue ex
|
rescue ex
|
||||||
videos = [] of PlaylistVideo
|
videos = [] of PlaylistVideo
|
||||||
end
|
end
|
||||||
@ -3010,6 +3017,17 @@ get "/api/v1/playlists/:plid" do |env|
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if format == "html"
|
||||||
|
response = JSON.parse(response)
|
||||||
|
playlist_html = template_playlist(response)
|
||||||
|
next_video = response["videos"].as_a[1]?.try &.["videoId"]
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"playlistHtml" => playlist_html,
|
||||||
|
"nextVideo" => next_video,
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
response
|
response
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -3021,6 +3039,9 @@ get "/api/v1/mixes/:rdid" do |env|
|
|||||||
continuation = env.params.query["continuation"]?
|
continuation = env.params.query["continuation"]?
|
||||||
continuation ||= rdid.lchop("RD")
|
continuation ||= rdid.lchop("RD")
|
||||||
|
|
||||||
|
format = env.params.query["format"]?
|
||||||
|
format ||= "json"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
mix = fetch_mix(rdid, continuation)
|
mix = fetch_mix(rdid, continuation)
|
||||||
rescue ex
|
rescue ex
|
||||||
@ -3059,6 +3080,17 @@ get "/api/v1/mixes/:rdid" do |env|
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if format == "html"
|
||||||
|
response = JSON.parse(response)
|
||||||
|
playlist_html = template_mix(response)
|
||||||
|
next_video = response["videos"].as_a[1]?.try &.["videoId"]
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"playlistHtml" => playlist_html,
|
||||||
|
"nextVideo" => next_video,
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
response
|
response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -35,6 +35,10 @@ def fetch_mix(rdid, video_id, cookies = nil)
|
|||||||
raise "Could not create mix."
|
raise "Could not create mix."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if !yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]
|
||||||
|
raise "Could not create mix."
|
||||||
|
end
|
||||||
|
|
||||||
playlist = yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"]
|
playlist = yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"]
|
||||||
mix_title = playlist["title"].as_s
|
mix_title = playlist["title"].as_s
|
||||||
|
|
||||||
@ -74,3 +78,37 @@ def fetch_mix(rdid, video_id, cookies = nil)
|
|||||||
videos = videos.first(50)
|
videos = videos.first(50)
|
||||||
return Mix.new(mix_title, rdid, videos)
|
return Mix.new(mix_title, rdid, videos)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def template_mix(mix)
|
||||||
|
html = <<-END_HTML
|
||||||
|
<h3>
|
||||||
|
<a href="/mix?list=#{mix["mixId"]}">
|
||||||
|
#{mix["title"]}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<div class="pure-menu pure-menu-scrollable playlist-restricted">
|
||||||
|
<ol class="pure-menu-list">
|
||||||
|
END_HTML
|
||||||
|
|
||||||
|
mix["videos"].as_a.each do |video|
|
||||||
|
html += <<-END_HTML
|
||||||
|
<li class="pure-menu-item">
|
||||||
|
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}">
|
||||||
|
<img style="width:100%;" src="/vi/#{video["videoId"]}/mqdefault.jpg">
|
||||||
|
<p style="width:100%">#{video["title"]}</p>
|
||||||
|
<p>
|
||||||
|
<b style="width: 100%">#{video["author"]}</b>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
html += <<-END_HTML
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
END_HTML
|
||||||
|
|
||||||
|
html
|
||||||
|
end
|
||||||
|
@ -26,11 +26,23 @@ class Playlist
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_playlist_videos(plid, page, video_count)
|
def fetch_playlist_videos(plid, page, video_count, continuation = nil)
|
||||||
client = make_client(YT_URL)
|
client = make_client(YT_URL)
|
||||||
|
|
||||||
if video_count > 100
|
if continuation
|
||||||
|
html = client.get("/watch?v=#{continuation}&list=#{plid}&bpctr=#{Time.new.epoch + 2000}&gl=US&hl=en&disable_polymer=1")
|
||||||
|
html = XML.parse_html(html.body)
|
||||||
|
|
||||||
|
index = html.xpath_node(%q(//span[@id="playlist-current-index"])).try &.content.to_i?
|
||||||
|
if index
|
||||||
|
index -= 1
|
||||||
|
end
|
||||||
|
index ||= 0
|
||||||
|
else
|
||||||
index = (page - 1) * 100
|
index = (page - 1) * 100
|
||||||
|
end
|
||||||
|
|
||||||
|
if video_count > 100
|
||||||
url = produce_playlist_url(plid, index)
|
url = produce_playlist_url(plid, index)
|
||||||
|
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
@ -199,3 +211,37 @@ def fetch_playlist(plid)
|
|||||||
|
|
||||||
return playlist
|
return playlist
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def template_playlist(playlist)
|
||||||
|
html = <<-END_HTML
|
||||||
|
<h3>
|
||||||
|
<a href="/playlist?list=#{playlist["playlistId"]}">
|
||||||
|
#{playlist["title"]}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<div class="pure-menu pure-menu-scrollable playlist-restricted">
|
||||||
|
<ol class="pure-menu-list">
|
||||||
|
END_HTML
|
||||||
|
|
||||||
|
playlist["videos"].as_a.each do |video|
|
||||||
|
html += <<-END_HTML
|
||||||
|
<li class="pure-menu-item">
|
||||||
|
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}">
|
||||||
|
<img style="width:100%;" src="/vi/#{video["videoId"]}/mqdefault.jpg">
|
||||||
|
<p style="width:100%">#{video["title"]}</p>
|
||||||
|
<p>
|
||||||
|
<b style="width: 100%">#{video["author"]}</b>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
html += <<-END_HTML
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
END_HTML
|
||||||
|
|
||||||
|
html
|
||||||
|
end
|
||||||
|
@ -123,6 +123,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1 pure-u-md-1-5">
|
<div class="pure-u-1 pure-u-md-1-5">
|
||||||
|
<% if plid %>
|
||||||
|
<div id="playlist" class="h-box">
|
||||||
|
<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if !preferences || preferences && preferences.related_videos %>
|
<% if !preferences || preferences && preferences.related_videos %>
|
||||||
<div class="h-box">
|
<div class="h-box">
|
||||||
<% rvs.each do |rv| %>
|
<% rvs.each do |rv| %>
|
||||||
@ -145,6 +152,61 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
<% if plid %>
|
||||||
|
function get_playlist() {
|
||||||
|
var plid = "<%= plid %>"
|
||||||
|
|
||||||
|
if (plid.startsWith("RD")) {
|
||||||
|
var plid_url = "/api/v1/mixes/<%= plid %>?continuation=<%= video.id %>&format=html";
|
||||||
|
} else {
|
||||||
|
var plid_url = "/api/v1/playlists/<%= plid %>?continuation=<%= video.id %>&format=html";
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = "json";
|
||||||
|
xhr.timeout = 20000;
|
||||||
|
xhr.open("GET", plid_url, true);
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
playlist = document.getElementById("playlist");
|
||||||
|
playlist.innerHTML = xhr.response.playlistHtml;
|
||||||
|
|
||||||
|
if (xhr.response.nextVideo) {
|
||||||
|
player.on('ended', function() {
|
||||||
|
window.location.replace("/watch?v="
|
||||||
|
+ xhr.response.nextVideo
|
||||||
|
+ "&list=<%= plid %>"
|
||||||
|
<% if params[:listen] %>
|
||||||
|
+ "&listen=1"
|
||||||
|
<% end %>
|
||||||
|
<% if params[:autoplay] %>
|
||||||
|
+ "&autoplay=1"
|
||||||
|
<% end %>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playlist.innerHTML = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
console.log("Pulling playlist timed out.");
|
||||||
|
|
||||||
|
comments = document.getElementById("playlist");
|
||||||
|
comments.innerHTML =
|
||||||
|
'<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3><hr>';
|
||||||
|
get_playlist();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get_playlist();
|
||||||
|
<% end %>
|
||||||
|
|
||||||
function get_reddit_comments() {
|
function get_reddit_comments() {
|
||||||
var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html";
|
var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html";
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
@ -154,7 +216,7 @@ function get_reddit_comments() {
|
|||||||
xhr.send();
|
xhr.send();
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.readyState == 4)
|
if (xhr.readyState == 4) {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
comments = document.getElementById("comments");
|
comments = document.getElementById("comments");
|
||||||
comments.innerHTML = ' \
|
comments.innerHTML = ' \
|
||||||
@ -188,6 +250,7 @@ function get_reddit_comments() {
|
|||||||
comments.innerHTML = "";
|
comments.innerHTML = "";
|
||||||
<% end %>
|
<% end %>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
xhr.ontimeout = function() {
|
||||||
@ -206,7 +269,7 @@ function get_youtube_comments() {
|
|||||||
xhr.send();
|
xhr.send();
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
if (xhr.readyState == 4)
|
if (xhr.readyState == 4) {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
comments = document.getElementById("comments");
|
comments = document.getElementById("comments");
|
||||||
if (xhr.response.commentCount > 0) {
|
if (xhr.response.commentCount > 0) {
|
||||||
@ -238,6 +301,7 @@ function get_youtube_comments() {
|
|||||||
comments.innerHTML = "";
|
comments.innerHTML = "";
|
||||||
<% end %>
|
<% end %>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
xhr.ontimeout = function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user