Add playlist playback support

This commit is contained in:
Omar Roth 2018-10-07 21:11:33 -05:00
parent c72b9bea64
commit 88430a6fc0
5 changed files with 190 additions and 5 deletions

View File

@ -17,6 +17,11 @@ div {
animation: spin 2s linear infinite; animation: spin 2s linear infinite;
} }
.playlist-restricted {
height: 20em;
padding-right: 10px;
}
/* /*
* Navbar * Navbar
*/ */

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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() {