Il proxy Rails gestisce il redirect MediaMTX su /live/match_* e riscrive le playlist; edge nginx reindirizza /live/ sotto /hls/. Su iOS, SessionBuilder HaishinKit, pipeline mixer corretta e refresh token. Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
3.2 KiB
Ruby
105 lines
3.2 KiB
Ruby
class HlsProxyController < ActionController::Base
|
|
# Proxy pubblico verso MediaMTX HLS.
|
|
# Dev: tutto passa da Rails (/hls/*).
|
|
# Prod: edge inoltra /hls/ a MediaMTX; il redirect cookie di MediaMTX punta a
|
|
# /live/match_{uuid}/… che deve essere gestito da #live_match (fallback).
|
|
def show
|
|
proxy_mediamtx_path(params[:path].to_s)
|
|
end
|
|
|
|
def live_match
|
|
session_id = params[:session_id].to_s
|
|
return head :not_found unless session_id.match?(/\A[0-9a-f-]{36}\z/i)
|
|
|
|
segments = params[:segments].presence || "index.m3u8"
|
|
proxy_mediamtx_path("live/match_#{session_id}/#{segments}")
|
|
end
|
|
|
|
private
|
|
|
|
def proxy_mediamtx_path(upstream_path)
|
|
return head :not_found if upstream_path.blank?
|
|
|
|
upstream = "#{MatchLiveTv.mediamtx_hls_url}/#{upstream_path}"
|
|
upstream = "#{upstream}?#{request.query_string}" if request.query_string.present?
|
|
cookie = request.headers["Cookie"].presence || "cookieCheck=1"
|
|
|
|
5.times do
|
|
response = hls_conn.get(upstream) { |req| req.headers["Cookie"] = cookie }
|
|
|
|
if response.status.in?([301, 302, 307, 308])
|
|
location = response.headers["location"].to_s
|
|
cookie = cookie_from_set_header(response.headers["set-cookie"]).presence || cookie
|
|
upstream = resolve_upstream_url(location)
|
|
next
|
|
end
|
|
|
|
return render_proxied_response(response)
|
|
end
|
|
|
|
head :bad_gateway
|
|
rescue Faraday::Error => e
|
|
Rails.logger.warn("[HlsProxy] #{upstream_path}: #{e.message}")
|
|
head :bad_gateway
|
|
end
|
|
|
|
def resolve_upstream_url(location)
|
|
return location if location.start_with?("http://", "https://")
|
|
|
|
base = MatchLiveTv.mediamtx_hls_url.sub(%r{/$}, "")
|
|
location.start_with?("/") ? "#{base}#{location}" : "#{base}/#{location}"
|
|
end
|
|
|
|
def render_proxied_response(response)
|
|
body = response.body.to_s
|
|
content_type = response.headers["content-type"].to_s
|
|
|
|
if m3u8_playlist?(content_type, body)
|
|
public_hls_base = "#{request.base_url}/hls"
|
|
body = body.gsub(%r{(?<=["'(\s])/live/(match_[0-9a-f-]{36}/)}, "/hls/live/\\1")
|
|
body = body.gsub(%r{\A/live/(match_[0-9a-f-]{36}/)}, "/hls/live/\\1")
|
|
end
|
|
|
|
response.headers.each do |key, value|
|
|
next if %w[transfer-encoding connection content-length content-encoding].include?(key.downcase)
|
|
|
|
if key.downcase == "location"
|
|
headers[key] = rewrite_public_location(value)
|
|
else
|
|
headers[key] = value
|
|
end
|
|
end
|
|
|
|
render body: body, status: response.status, content_type: content_type.presence
|
|
end
|
|
|
|
def rewrite_public_location(location)
|
|
loc = location.to_s
|
|
if loc.start_with?("/live/match_")
|
|
return loc.sub(%r{\A/live/}, "/hls/")
|
|
end
|
|
if loc.include?("/live/match_")
|
|
return loc.gsub(%r{/(live/match_[0-9a-f-]{36}/)}, "/hls/live/\\1")
|
|
end
|
|
loc
|
|
end
|
|
|
|
def m3u8_playlist?(content_type, body)
|
|
return true if content_type.include?("mpegurl") || content_type.include?("m3u8")
|
|
|
|
body.lstrip.start_with?("#EXTM3U")
|
|
end
|
|
|
|
def cookie_from_set_header(set_cookie_header)
|
|
set_cookie_header.to_s[/cookieCheck=[^;]+/]
|
|
end
|
|
|
|
def hls_conn
|
|
@hls_conn ||= Faraday.new do |f|
|
|
f.adapter Faraday.default_adapter
|
|
f.options.timeout = 15
|
|
f.options.open_timeout = 5
|
|
end
|
|
end
|
|
end
|