Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for HLS quality in user preferences #4118

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var options = {
html5: {
preloadTextTracks: false,
vhs: {
overrideNative: true
overrideNative: !videojs.browser.IS_ANY_SAFARI
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions config/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ https_only: false
## Disable proxying server-wide. Can be disable as a whole, or
## only for a single function.
##
## Accepted values: true, false, dash, livestreams, downloads, local
## Accepted values: true, false, dash, livestreams, downloads, local, hls
## Default: false
##
#disable_proxy: false
Expand Down Expand Up @@ -796,7 +796,7 @@ default_user_preferences:
##
## Default video quality.
##
## Accepted values: dash, hd720, medium, small
## Accepted values: hls, dash, hd720, medium, small
## Default: hd720
##
#quality: hd720
Expand Down
9 changes: 8 additions & 1 deletion locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"preferences_speed_label": "Default speed: ",
"preferences_quality_label": "Preferred video quality: ",
"preferences_quality_option_dash": "DASH (adaptive quality)",
"preferences_quality_option_hls": "HLS",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "Medium",
"preferences_quality_option_small": "Small",
Expand Down Expand Up @@ -498,5 +499,11 @@
"toggle_theme": "Toggle Theme",
"carousel_slide": "Slide {{current}} of {{total}}",
"carousel_skip": "Skip the Carousel",
"carousel_go_to": "Go to slide `x`"
"carousel_go_to": "Go to slide `x`",
"video_quality_livestream_label": "Livestream",
"video_quality_hls_label": "HLS",
"video_quality_dash_label": "DASH",
"video_quality_hd720_label": "hd720",
"video_quality_medium_label": "medium",
"video_quality_small_label": "small"
}
2 changes: 1 addition & 1 deletion src/invidious/routes/api/manifest.cr
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ module Invidious::Routes::API::Manifest
manifest = response.body

if local
manifest = manifest.gsub(/^https:\/\/\w+---.{11}\.c\.youtube\.com[^\n]*/m) do |match|
manifest = manifest.gsub(/https:\/\/\w+---.{11}(\.c\.youtube\.com|\.googlevideo\.com)[^\n]*/m) do |match|
path = URI.parse(match).path

path = path.lchop("/videoplayback/")
Expand Down
2 changes: 1 addition & 1 deletion src/invidious/routes/video_playback.cr
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ module Invidious::Routes::VideoPlayback
end

if url.includes? "&file=seg.ts"
if CONFIG.disabled?("livestreams")
if CONFIG.disabled?("livestreams") || CONFIG.disabled?("hls")
return error_template(403, "Administrator has disabled this endpoint.")
end

Expand Down
2 changes: 1 addition & 1 deletion src/invidious/routes/watch.cr
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module Invidious::Routes::Watch
env.params.query.delete_all("listen")

begin
video = get_video(id, region: params.region)
video = get_video(id, region: params.region, get_hls: (params.quality == "hls"))
rescue ex : NotFoundException
LOGGER.error("get_video not found: #{id} : #{ex.message}")
return error_template(404, ex)
Expand Down
15 changes: 14 additions & 1 deletion src/invidious/videos.cr
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ struct Video
predicate_bool upcoming, isUpcoming
end

def get_video(id, refresh = true, region = nil, force_refresh = false)
def get_video(id, refresh = true, region = nil, get_hls = false, force_refresh = false)
if (video = Invidious::Database::Videos.select(id)) && !region
# If record was last updated over 10 minutes ago, or video has since premiered,
# refresh (expire param in response lasts for 6 hours)
Expand All @@ -316,6 +316,19 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
Invidious::Database::Videos.insert(video) if !region
end

# The video object we got above could be from a previous request that was not
# done through the IOS client. If the users wants HLS we should check if
# a manifest exists in the data returned. If not we will rerequest one.
if get_hls && !video.hls_manifest_url
begin
video_with_hls_data = update_video_object_with_hls_data(id, video)
return video if !video_with_hls_data
Invidious::Database::Videos.update(video_with_hls_data) if !region
rescue ex
# Use old database video if IOS client request fails
end
end

return video
rescue DB::Error
# Avoid common `DB::PoolRetryAttemptsExceeded` error and friends
Expand Down
27 changes: 27 additions & 0 deletions src/invidious/videos/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ def extract_video_info(video_id : String)
# https://github.com/TeamNewPipe/NewPipeExtractor/issues/562
client_config.client_type = YoutubeAPI::ClientType::AndroidTestSuite
new_player_response = try_fetch_streaming_data(video_id, client_config)
else
if reason.nil?
# Fetch the video streams using an Android client in order to get the
# decrypted URLs and maybe fix throttling issues (#2194). See the
# following issue for an explanation about decrypted URLs:
# https://github.com/TeamNewPipe/NewPipeExtractor/issues/562
client_config.client_type = YoutubeAPI::ClientType::AndroidTestSuite
new_player_response = try_fetch_streaming_data(video_id, client_config)
end
end

# Replace player response and reset reason
Expand Down Expand Up @@ -144,6 +153,24 @@ def extract_video_info(video_id : String)
return params
end

def update_video_object_with_hls_data(id : String, video : Video)
client_config = YoutubeAPI::ClientConfig.new(client_type: YoutubeAPI::ClientType::IOS)

new_player_response = try_fetch_streaming_data(id, client_config)
current_streaming_data = video.info["streamingData"].try &.as_h

return nil if !new_player_response

if current_streaming_data && (manifest = new_player_response.dig?("streamingData", "hlsManifestUrl"))
current_streaming_data["hlsManifestUrl"] = JSON::Any.new(manifest.as_s)
video.info["streamingData"] = JSON::Any.new(current_streaming_data)

return video
end

return nil
end

def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.")
response = YoutubeAPI.player(video_id: id, params: "2AMB", client_config: client_config)
Expand Down
8 changes: 6 additions & 2 deletions src/invidious/videos/video_preferences.cr
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,12 @@ def process_video_params(query, preferences)
vr_mode = vr_mode == 1
save_player_pos = save_player_pos == 1

if CONFIG.disabled?("dash") && quality == "dash"
quality = "high"
# Force set quality to "high" if dash or hls has been disabled by the server
{"dash", "hls"}.each do |disabled_quality|
if CONFIG.disabled?(disabled_quality) && quality == disabled_quality
quality = "high"
break
end
end

if CONFIG.disabled?("local") && local
Expand Down
10 changes: 6 additions & 4 deletions src/invidious/views/components/player.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
<% if params.autoplay %>autoplay<% end %>
<% if params.video_loop %>loop<% end %>
<% if params.controls %>controls<% end %>>
<% if (hlsvp = video.hls_manifest_url) && !CONFIG.disabled?("livestreams") %>
<source src="<%= URI.parse(hlsvp).request_target %><% if params.local %>?local=true<% end %>" type="application/x-mpegURL" label="livestream">
<% if (hlsvp = video.hls_manifest_url) && video.live_now && !CONFIG.disabled?("livestreams") %>
<source src="<%= URI.parse(hlsvp).request_target %><% if params.local %>?local=true<% end %>" type="application/x-mpegURL" label="<%= HTML.escape(translate(locale,"video_quality_livestream_label")) %>">
<% elsif (hlsvp = video.hls_manifest_url) && params.quality == "hls" && !CONFIG.disabled?("hls") %>
<source src="<%= URI.parse(hlsvp).request_target %><% if params.local %>?local=true<% end %>" type="application/x-mpegURL" label="<%= HTML.escape(translate(locale,"video_quality_hls_label")) %>">
<% else %>
<% if params.listen %>
<% # default to 128k m4a stream
Expand Down Expand Up @@ -35,7 +37,7 @@
<% end %>
<% else %>
<% if params.quality == "dash" %>
<source src="/api/manifest/dash/id/<%= video.id %>?local=true&unique_res=1" type='application/dash+xml' label="dash">
<source src="/api/manifest/dash/id/<%= video.id %>?local=true&unique_res=1" type='application/dash+xml' label="<%= HTML.escape(translate(locale,"video_quality_dash_label")) %>">
<% end %>

<%
Expand All @@ -50,7 +52,7 @@

selected = params.quality ? (params.quality == quality) : (i == 0)
%>
<source src="<%= src_url %>" type="<%= mimetype %>" label="<%= quality %>" selected="<%= selected %>">
<source src="<%= src_url %>" type="<%= mimetype %>" label="<%= HTML.escape(translate(locale,"video_quality_#{quality}_label")) %>" selected="<%= selected %>">
<% if !params.local && !CONFIG.disabled?("local") %>
<source src="<%= src_url %>&local=true" type="<%= mimetype %>" hidequalityoption="true">
<% end %>
Expand Down
8 changes: 4 additions & 4 deletions src/invidious/views/user/preferences.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@
<div class="pure-control-group">
<label for="quality"><%= translate(locale, "preferences_quality_label") %></label>
<select name="quality" id="quality">
<% {"dash", "hd720", "medium", "small"}.each do |option| %>
<% if !(option == "dash" && CONFIG.disabled?("dash")) %>
<option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, "preferences_quality_option_" + option) %></option>
<% end %>
<% {"hls", "dash", "hd720", "medium", "small"}.each do |option| %>
<% next if (option == "dash" && CONFIG.disabled?("dash"))%>
<% next if (option == "hls" && CONFIG.disabled?("hls"))%>
<option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, "preferences_quality_option_" + option) %></option>
<% end %>
</select>
</div>
Expand Down
Loading