forked from midou/invidious
		
	Add playlist playback support
This commit is contained in:
		@@ -17,6 +17,11 @@ div {
 | 
			
		||||
  animation: spin 2s linear infinite;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.playlist-restricted {
 | 
			
		||||
  height: 20em;
 | 
			
		||||
  padding-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Navbar
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -217,6 +217,8 @@ get "/watch" do |env|
 | 
			
		||||
    next env.redirect "/"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  plid = env.params.query["list"]?
 | 
			
		||||
 | 
			
		||||
  user = env.get? "user"
 | 
			
		||||
  if 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 ||= 1
 | 
			
		||||
 | 
			
		||||
  format = env.params.query["format"]?
 | 
			
		||||
  format ||= "json"
 | 
			
		||||
 | 
			
		||||
  continuation = env.params.query["continuation"]?
 | 
			
		||||
 | 
			
		||||
  if plid.starts_with? "RD"
 | 
			
		||||
    next env.redirect "/api/v1/mixes/#{plid}"
 | 
			
		||||
  end
 | 
			
		||||
@@ -2951,7 +2958,7 @@ get "/api/v1/playlists/:plid" do |env|
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  begin
 | 
			
		||||
    videos = fetch_playlist_videos(plid, page, playlist.video_count)
 | 
			
		||||
    videos = fetch_playlist_videos(plid, page, playlist.video_count, continuation)
 | 
			
		||||
  rescue ex
 | 
			
		||||
    videos = [] of PlaylistVideo
 | 
			
		||||
  end
 | 
			
		||||
@@ -3010,6 +3017,17 @@ get "/api/v1/playlists/:plid" do |env|
 | 
			
		||||
    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
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@@ -3021,6 +3039,9 @@ get "/api/v1/mixes/:rdid" do |env|
 | 
			
		||||
  continuation = env.params.query["continuation"]?
 | 
			
		||||
  continuation ||= rdid.lchop("RD")
 | 
			
		||||
 | 
			
		||||
  format = env.params.query["format"]?
 | 
			
		||||
  format ||= "json"
 | 
			
		||||
 | 
			
		||||
  begin
 | 
			
		||||
    mix = fetch_mix(rdid, continuation)
 | 
			
		||||
  rescue ex
 | 
			
		||||
@@ -3059,6 +3080,17 @@ get "/api/v1/mixes/:rdid" do |env|
 | 
			
		||||
    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
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,10 @@ def fetch_mix(rdid, video_id, cookies = nil)
 | 
			
		||||
    raise "Could not create mix."
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  if !yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]
 | 
			
		||||
    raise "Could not create mix."
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  playlist = yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"]
 | 
			
		||||
  mix_title = playlist["title"].as_s
 | 
			
		||||
 | 
			
		||||
@@ -74,3 +78,37 @@ def fetch_mix(rdid, video_id, cookies = nil)
 | 
			
		||||
  videos = videos.first(50)
 | 
			
		||||
  return Mix.new(mix_title, rdid, videos)
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
def fetch_playlist_videos(plid, page, video_count)
 | 
			
		||||
def fetch_playlist_videos(plid, page, video_count, continuation = nil)
 | 
			
		||||
  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
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  if video_count > 100
 | 
			
		||||
    url = produce_playlist_url(plid, index)
 | 
			
		||||
 | 
			
		||||
    response = client.get(url)
 | 
			
		||||
@@ -199,3 +211,37 @@ def fetch_playlist(plid)
 | 
			
		||||
 | 
			
		||||
  return playlist
 | 
			
		||||
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 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 %>
 | 
			
		||||
        <div class="h-box">
 | 
			
		||||
        <% rvs.each do |rv| %>
 | 
			
		||||
@@ -145,6 +152,61 @@
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<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() {
 | 
			
		||||
  var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html";
 | 
			
		||||
  var xhr = new XMLHttpRequest();
 | 
			
		||||
@@ -154,7 +216,7 @@ function get_reddit_comments() {
 | 
			
		||||
  xhr.send();
 | 
			
		||||
 | 
			
		||||
  xhr.onreadystatechange = function() {
 | 
			
		||||
    if (xhr.readyState == 4)
 | 
			
		||||
    if (xhr.readyState == 4) {
 | 
			
		||||
      if (xhr.status == 200) {
 | 
			
		||||
        comments = document.getElementById("comments");
 | 
			
		||||
        comments.innerHTML = ' \
 | 
			
		||||
@@ -188,6 +250,7 @@ function get_reddit_comments() {
 | 
			
		||||
        comments.innerHTML = "";
 | 
			
		||||
        <% end %>
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  xhr.ontimeout = function() {
 | 
			
		||||
@@ -206,7 +269,7 @@ function get_youtube_comments() {
 | 
			
		||||
  xhr.send();
 | 
			
		||||
 | 
			
		||||
  xhr.onreadystatechange = function() {
 | 
			
		||||
    if (xhr.readyState == 4)
 | 
			
		||||
    if (xhr.readyState == 4) {
 | 
			
		||||
      if (xhr.status == 200) {
 | 
			
		||||
        comments = document.getElementById("comments");
 | 
			
		||||
        if (xhr.response.commentCount > 0)  {
 | 
			
		||||
@@ -238,6 +301,7 @@ function get_youtube_comments() {
 | 
			
		||||
        comments.innerHTML = "";
 | 
			
		||||
        <% end %>
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  xhr.ontimeout = function() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user