diff --git a/assets/js/player.js b/assets/js/player.js index 353a52963..3f6d8ff35 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -25,7 +25,7 @@ var options = { html5: { preloadTextTracks: false, vhs: { - overrideNative: true + overrideNative: !videojs.browser.IS_ANY_SAFARI } } }; diff --git a/config/config.example.yml b/config/config.example.yml index a3a2eeb76..81b7bc122 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -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 @@ -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 diff --git a/locales/en-US.json b/locales/en-US.json index c23f6bc33..3693f1505 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -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", @@ -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" } diff --git a/src/invidious/routes/api/manifest.cr b/src/invidious/routes/api/manifest.cr index d89e752cd..0b2025423 100644 --- a/src/invidious/routes/api/manifest.cr +++ b/src/invidious/routes/api/manifest.cr @@ -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/") diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr index 26852d068..29c43f536 100644 --- a/src/invidious/routes/video_playback.cr +++ b/src/invidious/routes/video_playback.cr @@ -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 diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index aabe8dfc2..d0fcf6622 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -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) diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index ae09e736e..6787b957b 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -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) @@ -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 diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index 915c9baf8..1dba5440a 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -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 @@ -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) diff --git a/src/invidious/videos/video_preferences.cr b/src/invidious/videos/video_preferences.cr index 48177bd88..79b510d85 100644 --- a/src/invidious/videos/video_preferences.cr +++ b/src/invidious/videos/video_preferences.cr @@ -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 diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index 5c28358b4..d75202cbc 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -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") %> - + <% if (hlsvp = video.hls_manifest_url) && video.live_now && !CONFIG.disabled?("livestreams") %> + "> + <% elsif (hlsvp = video.hls_manifest_url) && params.quality == "hls" && !CONFIG.disabled?("hls") %> + "> <% else %> <% if params.listen %> <% # default to 128k m4a stream @@ -35,7 +37,7 @@ <% end %> <% else %> <% if params.quality == "dash" %> - + "> <% end %> <% @@ -50,7 +52,7 @@ selected = params.quality ? (params.quality == quality) : (i == 0) %> - + " selected="<%= selected %>"> <% if !params.local && !CONFIG.disabled?("local") %> <% end %> diff --git a/src/invidious/views/user/preferences.ecr b/src/invidious/views/user/preferences.ecr index cf8b55936..4927c63b1 100644 --- a/src/invidious/views/user/preferences.ecr +++ b/src/invidious/views/user/preferences.ecr @@ -54,10 +54,10 @@