Fix HLS browser in prod e stabilizza broadcast RTMP su iOS.

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>
This commit is contained in:
Emiliano Frascaro
2026-06-19 18:48:29 +02:00
parent ed0f913138
commit a303a94671
16 changed files with 1092 additions and 319 deletions

View File

@@ -1,38 +1,98 @@
class HlsProxyController < ActionController::Base
# Proxy pubblico verso MediaMTX HLS (NPM inoltra tutto a Rails :3000).
# 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
upstream_path = params[:path].to_s
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 == 302
if response.status.in?([301, 302, 307, 308])
location = response.headers["location"].to_s
follow_url = if location.start_with?("http")
location
else
"#{MatchLiveTv.mediamtx_hls_url.sub(%r{/$}, "")}#{location}"
end
set_cookie = response.headers["set-cookie"].to_s
cookie = set_cookie.presence || cookie
response = hls_conn.get(follow_url) { |req| req.headers["Cookie"] = cookie }
cookie = cookie_from_set_header(response.headers["set-cookie"]).presence || cookie
upstream = resolve_upstream_url(location)
next
end
response.headers.each do |key, value|
next if %w[transfer-encoding connection].include?(key.downcase)
headers[key] = value
return render_proxied_response(response)
end
render body: response.body, status: response.status
head :bad_gateway
rescue Faraday::Error => e
Rails.logger.warn("[HlsProxy] #{upstream_path}: #{e.message}")
head :bad_gateway
end
private
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|

View File

@@ -13,7 +13,8 @@ Rails.application.configure do
config.ssl_options = {
redirect: {
exclude: ->(request) {
request.path == "/up" || request.path == "/up/deep" || request.path == "/cable" || request.path.start_with?("/hls/")
request.path == "/up" || request.path == "/up/deep" || request.path == "/cable" ||
request.path.start_with?("/hls/") || request.path.match?(%r{\A/live/match_[0-9a-f-]{36}/}i)
}
}
}
@@ -34,7 +35,8 @@ Rails.application.configure do
config.host_authorization = {
exclude: ->(request) {
request.path == "/up" || request.path == "/up/deep" || request.path.start_with?("/cable") || request.path.start_with?("/hls/")
request.path == "/up" || request.path == "/up/deep" || request.path.start_with?("/cable") ||
request.path.start_with?("/hls/") || request.path.match?(%r{\A/live/match_[0-9a-f-]{36}/}i)
}
}

View File

@@ -109,6 +109,16 @@ Rails.application.routes.draw do
end
get "hls/*path", to: "hls_proxy#show", format: false, constraints: { path: /.+/ }
# MediaMTX HLS cookie redirect → /live/match_{uuid}/… (edge o browser dopo 302).
get "live/match_:session_id",
to: "hls_proxy#live_match",
format: false,
constraints: { session_id: /[0-9a-f-]{36}/i },
defaults: { segments: "index.m3u8" }
get "live/match_:session_id/*segments",
to: "hls_proxy#live_match",
format: false,
constraints: { session_id: /[0-9a-f-]{36}/i, segments: /.+/ }
get "live", to: "public/live#index", as: :public_live_index
get "live/:id", to: "public/live#show", as: :public_live

View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true
require "rails_helper"
RSpec.describe "HLS proxy", type: :request do
let(:mediamtx_hls) { "http://mediamtx.test:8888" }
let(:playlist) do
<<~M3U8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:1
#EXTINF:1.0,
/live/match_#{session_id}/segment0.ts
M3U8
end
let(:session_id) { "cc7e90b2-c672-4401-b5d3-51fdcdc7a214" }
before do
allow(MatchLiveTv).to receive(:mediamtx_hls_url).and_return(mediamtx_hls)
end
describe "GET /live/match_:session_id/*" do
it "proxies MediaMTX redirect and rewrites playlist paths to /hls/" do
stub_request(:get, "#{mediamtx_hls}/live/match_#{session_id}/index.m3u8")
.to_return(status: 302, headers: { "Location" => "/live/match_#{session_id}/index.m3u8?cookieCheck=1" })
stub_request(:get, "#{mediamtx_hls}/live/match_#{session_id}/index.m3u8?cookieCheck=1")
.to_return(status: 200, body: playlist, headers: { "Content-Type" => "application/vnd.apple.mpegurl" })
get "/live/match_#{session_id}/index.m3u8"
expect(response).to have_http_status(:ok)
expect(response.body).to include("/hls/live/match_#{session_id}/segment0.ts")
expect(response.body).not_to include("/live/match_#{session_id}/segment0.ts")
end
end
end

View File

@@ -67,6 +67,31 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# MediaMTX risponde 302 verso /live/match_{uuid}/… — riscrive per restare sotto /hls/
proxy_redirect /live/ /hls/live/;
proxy_redirect ~^/live/(.*)$ /hls/live/$1;
}
# Fallback: se il browser segue un redirect assoluto a /live/match_{uuid}/… (m3u8/ts).
location ~ ^/live/match_[0-9a-f-]+/ {
limit_except GET HEAD {
deny all;
}
rewrite ^/live/(.*)$ /$1 break;
proxy_pass http://mediamtx:8888;
proxy_http_version 1.1;
proxy_buffering off;
proxy_request_buffering off;
proxy_connect_timeout 5s;
proxy_read_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header Cookie $hls_cookie;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect /live/ /hls/live/;
proxy_redirect ~^/live/(.*)$ /hls/live/$1;
}
location = /edge-health {

View File

@@ -1,240 +1,625 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {};
classes = {
};
objectVersion = 60;
objects = {
2C1637685ECE4A02B5681E5F /* MatchLiveTvApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveTvApp.swift; path = MatchLiveTv/App/MatchLiveTvApp.swift; sourceTree = "<group>"; };
56472459E69946E8AE093248 /* ApiInstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstant.swift; path = MatchLiveTv/Core/ApiInstant.swift; sourceTree = "<group>"; };
059D2F48669B4073BFE72E8D /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfig.swift; path = MatchLiveTv/Core/AppConfig.swift; sourceTree = "<group>"; };
8F12B9A34CFD41029CD74A18 /* ColorHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ColorHex.swift; path = MatchLiveTv/Core/ColorHex.swift; sourceTree = "<group>"; };
6D1122BE2CAB4C6C9D6A12EB /* DeviceTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DeviceTelemetry.swift; path = MatchLiveTv/Core/DeviceTelemetry.swift; sourceTree = "<group>"; };
1FD3F6FCA8B54DDA858ABB6D /* MediaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaUrl.swift; path = MatchLiveTv/Core/MediaUrl.swift; sourceTree = "<group>"; };
22FD8122DEC74182A2F4CD69 /* TokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TokenStore.swift; path = MatchLiveTv/Core/TokenStore.swift; sourceTree = "<group>"; };
58C37FC10F34409EB32CAB94 /* UserFacingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserFacingError.swift; path = MatchLiveTv/Core/UserFacingError.swift; sourceTree = "<group>"; };
229A7B6E2AF14254BE5E1BE7 /* ApiDtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiDtos.swift; path = MatchLiveTv/Data/API/ApiDtos.swift; sourceTree = "<group>"; };
92CE832D69994A86B2CA85AD /* MatchLiveAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveAPI.swift; path = MatchLiveTv/Data/API/MatchLiveAPI.swift; sourceTree = "<group>"; };
9AB29B550B774276AA6508CB /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppContainer.swift; path = MatchLiveTv/Data/AppContainer.swift; sourceTree = "<group>"; };
9F97D0F58B0C4232827A6FEA /* ActionCableClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ActionCableClient.swift; path = MatchLiveTv/Data/Cable/ActionCableClient.swift; sourceTree = "<group>"; };
BE6073F255774BD1AB25D01A /* SessionCableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionCableService.swift; path = MatchLiveTv/Data/Cable/SessionCableService.swift; sourceTree = "<group>"; };
6E79EB14DEDE4BBBB30734E1 /* AuthRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthRepository.swift; path = MatchLiveTv/Data/Repository/AuthRepository.swift; sourceTree = "<group>"; };
606456B638DD4D8F8FEAF57B /* MatchRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchRepository.swift; path = MatchLiveTv/Data/Repository/MatchRepository.swift; sourceTree = "<group>"; };
562CD8B2B0DA4275BAA14FE3 /* MatchSessionLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSessionLauncher.swift; path = MatchLiveTv/Data/Repository/MatchSessionLauncher.swift; sourceTree = "<group>"; };
C9718FB64F514448BB5F8498 /* ScoreRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreRepository.swift; path = MatchLiveTv/Data/Repository/ScoreRepository.swift; sourceTree = "<group>"; };
149E69E5291642339CFAC14B /* SessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionRepository.swift; path = MatchLiveTv/Data/Repository/SessionRepository.swift; sourceTree = "<group>"; };
4C53F1C734CB4745BCB4BBD0 /* ScoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreController.swift; path = MatchLiveTv/Data/Scoring/ScoreController.swift; sourceTree = "<group>"; };
DF85D2BF2D574EEF97215552 /* WizardSessionHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardSessionHolder.swift; path = MatchLiveTv/Data/WizardSessionHolder.swift; sourceTree = "<group>"; };
AA323B2E80B74C8DBEE4DA45 /* MatchHubFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchHubFilter.swift; path = MatchLiveTv/Domain/MatchHubFilter.swift; sourceTree = "<group>"; };
4BF8D73247554EAF8506B4C8 /* MatchScoringRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRules.swift; path = MatchLiveTv/Domain/MatchScoringRules.swift; sourceTree = "<group>"; };
7AE2FC4135BB4D0A82B4DFFC /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Models.swift; path = MatchLiveTv/Domain/Models.swift; sourceTree = "<group>"; };
126835B4E56A4AD0B3119429 /* ScoreState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreState.swift; path = MatchLiveTv/Domain/ScoreState.swift; sourceTree = "<group>"; };
31DA29870CD24D749044570D /* BroadcastModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastModels.swift; path = MatchLiveTv/Streaming/BroadcastModels.swift; sourceTree = "<group>"; };
4B0C6EE1A67440D5B5B8EC2F /* LiveBroadcastCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinator.swift; path = MatchLiveTv/Streaming/LiveBroadcastCoordinator.swift; sourceTree = "<group>"; };
88CCBA95967F429093C28846 /* LiveBroadcastEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastEngine.swift; path = MatchLiveTv/Streaming/LiveBroadcastEngine.swift; sourceTree = "<group>"; };
E3EA9302512B4A5AB283D88E /* CompactScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CompactScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/CompactScoreboardElement.swift; sourceTree = "<group>"; };
F7E85AC8EC7348F4A5B21F94 /* OverlayCanvasRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayCanvasRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayCanvasRenderer.swift; sourceTree = "<group>"; };
87D71E37017C47799B602656 /* OverlayLogoCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayLogoCache.swift; path = MatchLiveTv/Streaming/Overlay/OverlayLogoCache.swift; sourceTree = "<group>"; };
73D8BF04B48E405BABA5395C /* OverlayMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayMappings.swift; path = MatchLiveTv/Streaming/Overlay/OverlayMappings.swift; sourceTree = "<group>"; };
8854A01335904882AADB4938 /* OverlayRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayRenderer.swift; sourceTree = "<group>"; };
6CA512B24B2048DAB9876C7F /* OverlayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayState.swift; path = MatchLiveTv/Streaming/Overlay/OverlayState.swift; sourceTree = "<group>"; };
0DA1CA2637BC43338153CC1D /* ScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/ScoreboardElement.swift; sourceTree = "<group>"; };
58668C3993F243FD9A5621C6 /* SponsorElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SponsorElement.swift; path = MatchLiveTv/Streaming/Overlay/SponsorElement.swift; sourceTree = "<group>"; };
E1722E92B37E4FB5BFA0F64F /* WatermarkElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WatermarkElement.swift; path = MatchLiveTv/Streaming/Overlay/WatermarkElement.swift; sourceTree = "<group>"; };
6407B9289F0048778B2A6057 /* LivePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LivePreviewView.swift; path = MatchLiveTv/Streaming/Preview/LivePreviewView.swift; sourceTree = "<group>"; };
E44F2780AEDA44F8B16091E3 /* BroadcastControlsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastControlsOverlay.swift; path = MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift; sourceTree = "<group>"; };
106A45AE33B94E778FDEDED7 /* BroadcastScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastScreen.swift; path = MatchLiveTv/UI/Broadcast/BroadcastScreen.swift; sourceTree = "<group>"; };
31FAA2DF5F114464977D89B0 /* LiveScoreActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreActions.swift; path = MatchLiveTv/UI/Broadcast/LiveScoreActions.swift; sourceTree = "<group>"; };
3CA6FD7B0F1E412FA56AB8A3 /* MatchLiveWordmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveWordmark.swift; path = MatchLiveTv/UI/Components/MatchLiveWordmark.swift; sourceTree = "<group>"; };
9545AF93C34540748A1C6E33 /* MatchPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchPrimaryButton.swift; path = MatchLiveTv/UI/Components/MatchPrimaryButton.swift; sourceTree = "<group>"; };
8AD4558FC0EB4A5D99F41E4D /* MatchScreenScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScreenScaffold.swift; path = MatchLiveTv/UI/Components/MatchScreenScaffold.swift; sourceTree = "<group>"; };
091497DB283544DFA9C7E736 /* MatchSecondaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSecondaryButton.swift; path = MatchLiveTv/UI/Components/MatchSecondaryButton.swift; sourceTree = "<group>"; };
F93A551F9C8E4A71A007F25C /* MatchStatusBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchStatusBadge.swift; path = MatchLiveTv/UI/Components/MatchStatusBadge.swift; sourceTree = "<group>"; };
66AF9547CF434A4D9C1E8335 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginScreen.swift; path = MatchLiveTv/UI/Login/LoginScreen.swift; sourceTree = "<group>"; };
F32269FB35364D969738C23A /* MatchesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchesScreen.swift; path = MatchLiveTv/UI/Matches/MatchesScreen.swift; sourceTree = "<group>"; };
00BDEB10F16C4F968EEEF50E /* AppNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNavHost.swift; path = MatchLiveTv/UI/Navigation/AppNavHost.swift; sourceTree = "<group>"; };
A99587B489BF4900B86754ED /* ModalRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ModalRoutes.swift; path = MatchLiveTv/UI/Navigation/ModalRoutes.swift; sourceTree = "<group>"; };
E013E12B974B4FCCA3C74151 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Routes.swift; path = MatchLiveTv/UI/Navigation/Routes.swift; sourceTree = "<group>"; };
576978DEF79042848BCA07DC /* BroadcastPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastPermissions.swift; path = MatchLiveTv/UI/Permissions/BroadcastPermissions.swift; sourceTree = "<group>"; };
BEC9E21F02BC41D58E006F66 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplashScreen.swift; path = MatchLiveTv/UI/Splash/SplashScreen.swift; sourceTree = "<group>"; };
64BB42C4455341CBBEF7ADB1 /* KeepScreenOn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = KeepScreenOn.swift; path = MatchLiveTv/UI/System/KeepScreenOn.swift; sourceTree = "<group>"; };
7AFF6FEE9E2C420DB0B69CDC /* ScreenOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScreenOrientation.swift; path = MatchLiveTv/UI/System/ScreenOrientation.swift; sourceTree = "<group>"; };
EBAE4EB3BE0141AD982E1D5E /* MatchColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchColors.swift; path = MatchLiveTv/UI/Theme/MatchColors.swift; sourceTree = "<group>"; };
6CE9AE7967074219B69DFEE4 /* StepMatchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepMatchScreen.swift; path = MatchLiveTv/UI/Wizard/StepMatchScreen.swift; sourceTree = "<group>"; };
40229139B1934E469D211B4F /* StepNetworkTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepNetworkTestScreen.swift; path = MatchLiveTv/UI/Wizard/StepNetworkTestScreen.swift; sourceTree = "<group>"; };
7F4BEC0641C14E6FBCAF4163 /* StepTransmissionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepTransmissionScreen.swift; path = MatchLiveTv/UI/Wizard/StepTransmissionScreen.swift; sourceTree = "<group>"; };
494C6DACBCA34021BAB851B9 /* TeamBrandingEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamBrandingEditor.swift; path = MatchLiveTv/UI/Wizard/TeamBrandingEditor.swift; sourceTree = "<group>"; };
AEB1E4F84BBB438AA1FE8BEC /* TeamColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamColorPicker.swift; path = MatchLiveTv/UI/Wizard/TeamColorPicker.swift; sourceTree = "<group>"; };
CF166707120B4B589F342EC4 /* WizardComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardComponents.swift; path = MatchLiveTv/UI/Wizard/WizardComponents.swift; sourceTree = "<group>"; };
5C742326421B4176855F5E64 /* WizardShellScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardShellScreen.swift; path = MatchLiveTv/UI/Wizard/WizardShellScreen.swift; sourceTree = "<group>"; };
EA7B4BCC9EAA48CF94ADF09F /* ApiInstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstantTests.swift; path = MatchLiveTvTests/ApiInstantTests.swift; sourceTree = "<group>"; };
596D2E6129214E95874E4E1F /* MatchScoringRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRulesTests.swift; path = MatchLiveTvTests/MatchScoringRulesTests.swift; sourceTree = "<group>"; };
82C7043D46EB4FBCB41BFB87 /* ScoreActionDecodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreActionDecodeTests.swift; path = MatchLiveTvTests/ScoreActionDecodeTests.swift; sourceTree = "<group>"; };
1F95F2F95BBB45589765B6C1 /* ScoreControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreControllerTests.swift; path = MatchLiveTvTests/ScoreControllerTests.swift; sourceTree = "<group>"; };
7B6C9A5E9BA84A6BBCBF9B3F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MatchLiveTv/Resources/Assets.xcassets; sourceTree = "<group>"; };
D1A10874BEB246908DF27543 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MatchLiveTv/Resources/Info.plist; sourceTree = "<group>"; };
9989F3DEB021437FB1D79792 /* MatchLiveTv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchLiveTv.app; sourceTree = BUILT_PRODUCTS_DIR; };
6F0D757BA4DF4A8BAA37E5A1 /* MatchLiveTvTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchLiveTvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1794E4AFEC524A2391E1F43A /* HaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 144AB99B1DAD4D48925A7103; productName = HaishinKit; };
5A2B53779BB342F0A4EB1D50 /* RTMPHaishinKit */ = {isa = XCSwiftPackageProductDependency; package = 144AB99B1DAD4D48925A7103; productName = RTMPHaishinKit; };
63AA6E164A9D46F08CE75059 /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1637685ECE4A02B5681E5F /* MatchLiveTvApp.swift */; };
9F4B82FECE1B4479BD5C70EA /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56472459E69946E8AE093248 /* ApiInstant.swift */; };
9F22BE9935F84C4F9B6DA971 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 059D2F48669B4073BFE72E8D /* AppConfig.swift */; };
6EB025E38C2E45C985C7E315 /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F12B9A34CFD41029CD74A18 /* ColorHex.swift */; };
EE457ABEF9B74DA28A2E2FFD /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D1122BE2CAB4C6C9D6A12EB /* DeviceTelemetry.swift */; };
2D699C6ABD2840B385194046 /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD3F6FCA8B54DDA858ABB6D /* MediaUrl.swift */; };
2BF572CC8FBB420CAE651946 /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FD8122DEC74182A2F4CD69 /* TokenStore.swift */; };
71B88EC9E30A4866B3F7E20E /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C37FC10F34409EB32CAB94 /* UserFacingError.swift */; };
0461E337AAD54BBFA537E7E1 /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229A7B6E2AF14254BE5E1BE7 /* ApiDtos.swift */; };
1BFEEBA978A54FC8A1A38D7D /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92CE832D69994A86B2CA85AD /* MatchLiveAPI.swift */; };
4A170EAF1F114BA1BD512C77 /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB29B550B774276AA6508CB /* AppContainer.swift */; };
8FCD261732D245B69839DAEB /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F97D0F58B0C4232827A6FEA /* ActionCableClient.swift */; };
A967247B226E4E86811D86DA /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6073F255774BD1AB25D01A /* SessionCableService.swift */; };
25360BD296FA4D26AB936847 /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E79EB14DEDE4BBBB30734E1 /* AuthRepository.swift */; };
77FF5239FE2A458F815117E5 /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606456B638DD4D8F8FEAF57B /* MatchRepository.swift */; };
4CE31F1DC2A141BB97BBBF77 /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 562CD8B2B0DA4275BAA14FE3 /* MatchSessionLauncher.swift */; };
C741D7BBAEF842B1A5CE79F5 /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9718FB64F514448BB5F8498 /* ScoreRepository.swift */; };
033E9E19FA1D41ECBC6FE954 /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 149E69E5291642339CFAC14B /* SessionRepository.swift */; };
9708F347932F4ED9B25F30D4 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C53F1C734CB4745BCB4BBD0 /* ScoreController.swift */; };
6F21F50CE3394618BBDA9DB9 /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF85D2BF2D574EEF97215552 /* WizardSessionHolder.swift */; };
E1EBDFB0867C415FBC6D1267 /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA323B2E80B74C8DBEE4DA45 /* MatchHubFilter.swift */; };
43B0ABA8C73547FBBA33F681 /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D73247554EAF8506B4C8 /* MatchScoringRules.swift */; };
9B29D4ECB04641B386D43176 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE2FC4135BB4D0A82B4DFFC /* Models.swift */; };
9FD1B70E6BC94A5B963C84E3 /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 126835B4E56A4AD0B3119429 /* ScoreState.swift */; };
53A1BD84C77844F9A5322D71 /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DA29870CD24D749044570D /* BroadcastModels.swift */; };
55822A3378244F328DEC930B /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0C6EE1A67440D5B5B8EC2F /* LiveBroadcastCoordinator.swift */; };
8C3A6D2934054AD685C6B17B /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88CCBA95967F429093C28846 /* LiveBroadcastEngine.swift */; };
BB282D2B90F442FAA19957DE /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EA9302512B4A5AB283D88E /* CompactScoreboardElement.swift */; };
12D1F6F6647E4A02B5FAE6A5 /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E85AC8EC7348F4A5B21F94 /* OverlayCanvasRenderer.swift */; };
259CD50B331D435CAA934791 /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D71E37017C47799B602656 /* OverlayLogoCache.swift */; };
597F0E27F4794C5B8EB4421D /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D8BF04B48E405BABA5395C /* OverlayMappings.swift */; };
4CB7FA4311CD496DBAF590D2 /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8854A01335904882AADB4938 /* OverlayRenderer.swift */; };
124E11045F024DB79725B158 /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA512B24B2048DAB9876C7F /* OverlayState.swift */; };
00E66D28E2C44685B08D3862 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA1CA2637BC43338153CC1D /* ScoreboardElement.swift */; };
0D823575D3C34A6AB0B1ADE7 /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58668C3993F243FD9A5621C6 /* SponsorElement.swift */; };
6826A0C8849B468089F89095 /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1722E92B37E4FB5BFA0F64F /* WatermarkElement.swift */; };
85DD60C1258B4454879F3958 /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407B9289F0048778B2A6057 /* LivePreviewView.swift */; };
33E82511BEF2435A832885B6 /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44F2780AEDA44F8B16091E3 /* BroadcastControlsOverlay.swift */; };
63A3704385234F25B6B4D9DE /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106A45AE33B94E778FDEDED7 /* BroadcastScreen.swift */; };
DF93C479029448FB98BB26D4 /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FAA2DF5F114464977D89B0 /* LiveScoreActions.swift */; };
1E7B2D233349405B9D8850B8 /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA6FD7B0F1E412FA56AB8A3 /* MatchLiveWordmark.swift */; };
573B97F0E4D74DCD90962950 /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9545AF93C34540748A1C6E33 /* MatchPrimaryButton.swift */; };
4B7C1844987C4C4A9331EFF6 /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD4558FC0EB4A5D99F41E4D /* MatchScreenScaffold.swift */; };
5791C4F50F0948E7993907B3 /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091497DB283544DFA9C7E736 /* MatchSecondaryButton.swift */; };
2E8C344A733B462A87720E91 /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93A551F9C8E4A71A007F25C /* MatchStatusBadge.swift */; };
2FB16045C6BD4A5E889DD484 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF9547CF434A4D9C1E8335 /* LoginScreen.swift */; };
029149C11EA642BEBEA697D8 /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32269FB35364D969738C23A /* MatchesScreen.swift */; };
C9B7DFB707744641BD6CED07 /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00BDEB10F16C4F968EEEF50E /* AppNavHost.swift */; };
A4D5F108E5374A108B5860B1 /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99587B489BF4900B86754ED /* ModalRoutes.swift */; };
F04BF893D3C248C2AF7A546A /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E013E12B974B4FCCA3C74151 /* Routes.swift */; };
7B19B54BCD0047F980097559 /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 576978DEF79042848BCA07DC /* BroadcastPermissions.swift */; };
814C7713F99942A896722EEE /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEC9E21F02BC41D58E006F66 /* SplashScreen.swift */; };
BE9617BC3F2748A2B78AFF9A /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BB42C4455341CBBEF7ADB1 /* KeepScreenOn.swift */; };
1341F3BB59E24B7DA0B3F492 /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFF6FEE9E2C420DB0B69CDC /* ScreenOrientation.swift */; };
DA875E6F04BA46C6BB5193DB /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBAE4EB3BE0141AD982E1D5E /* MatchColors.swift */; };
B95B2FF16A294D2A82E74BC9 /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE9AE7967074219B69DFEE4 /* StepMatchScreen.swift */; };
0AB2C536294B411EA229CFFF /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40229139B1934E469D211B4F /* StepNetworkTestScreen.swift */; };
4EAEC68278E6487496AC81EE /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F4BEC0641C14E6FBCAF4163 /* StepTransmissionScreen.swift */; };
C39AC5E9671941D89416F412 /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494C6DACBCA34021BAB851B9 /* TeamBrandingEditor.swift */; };
68C7A87D2C5E49F2A7BCEF99 /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEB1E4F84BBB438AA1FE8BEC /* TeamColorPicker.swift */; };
85563DC5408842109B436A49 /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF166707120B4B589F342EC4 /* WizardComponents.swift */; };
5DE5E7C500304E16915076E3 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C742326421B4176855F5E64 /* WizardShellScreen.swift */; };
0CB7A2DEBA8041A5B75A2F74 /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7B4BCC9EAA48CF94ADF09F /* ApiInstantTests.swift */; };
B97B7A37B48540A8BB2653A7 /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596D2E6129214E95874E4E1F /* MatchScoringRulesTests.swift */; };
34968BB5B4FD4727A1DB7DA6 /* ScoreActionDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C7043D46EB4FBCB41BFB87 /* ScoreActionDecodeTests.swift */; };
1FB1035FC78C4034BBC93FEF /* ScoreControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F95F2F95BBB45589765B6C1 /* ScoreControllerTests.swift */; };
5A54FCC515114BE19610F99F /* Assets in Resources */ = {isa = PBXBuildFile; fileRef = 7B6C9A5E9BA84A6BBCBF9B3F; };
9E10ADDF29394FD6B200D2A7 = {isa = PBXGroup; children = (3181CAF4010E480AB325DA30, 61A4D30FDB7644FEBBCB6AC7, 948F72C223D64471ABFCFE2F); sourceTree = "<group>"; };
3181CAF4010E480AB325DA30 = {isa = PBXGroup; children = (2C1637685ECE4A02B5681E5F, 56472459E69946E8AE093248, 059D2F48669B4073BFE72E8D, 8F12B9A34CFD41029CD74A18, 6D1122BE2CAB4C6C9D6A12EB, 1FD3F6FCA8B54DDA858ABB6D, 22FD8122DEC74182A2F4CD69, 58C37FC10F34409EB32CAB94, 229A7B6E2AF14254BE5E1BE7, 92CE832D69994A86B2CA85AD, 9AB29B550B774276AA6508CB, 9F97D0F58B0C4232827A6FEA, BE6073F255774BD1AB25D01A, 6E79EB14DEDE4BBBB30734E1, 606456B638DD4D8F8FEAF57B, 562CD8B2B0DA4275BAA14FE3, C9718FB64F514448BB5F8498, 149E69E5291642339CFAC14B, 4C53F1C734CB4745BCB4BBD0, DF85D2BF2D574EEF97215552, AA323B2E80B74C8DBEE4DA45, 4BF8D73247554EAF8506B4C8, 7AE2FC4135BB4D0A82B4DFFC, 126835B4E56A4AD0B3119429, 31DA29870CD24D749044570D, 4B0C6EE1A67440D5B5B8EC2F, 88CCBA95967F429093C28846, E3EA9302512B4A5AB283D88E, F7E85AC8EC7348F4A5B21F94, 87D71E37017C47799B602656, 73D8BF04B48E405BABA5395C, 8854A01335904882AADB4938, 6CA512B24B2048DAB9876C7F, 0DA1CA2637BC43338153CC1D, 58668C3993F243FD9A5621C6, E1722E92B37E4FB5BFA0F64F, 6407B9289F0048778B2A6057, E44F2780AEDA44F8B16091E3, 106A45AE33B94E778FDEDED7, 31FAA2DF5F114464977D89B0, 3CA6FD7B0F1E412FA56AB8A3, 9545AF93C34540748A1C6E33, 8AD4558FC0EB4A5D99F41E4D, 091497DB283544DFA9C7E736, F93A551F9C8E4A71A007F25C, 66AF9547CF434A4D9C1E8335, F32269FB35364D969738C23A, 00BDEB10F16C4F968EEEF50E, A99587B489BF4900B86754ED, E013E12B974B4FCCA3C74151, 576978DEF79042848BCA07DC, BEC9E21F02BC41D58E006F66, 64BB42C4455341CBBEF7ADB1, 7AFF6FEE9E2C420DB0B69CDC, EBAE4EB3BE0141AD982E1D5E, 6CE9AE7967074219B69DFEE4, 40229139B1934E469D211B4F, 7F4BEC0641C14E6FBCAF4163, 494C6DACBCA34021BAB851B9, AEB1E4F84BBB438AA1FE8BEC, CF166707120B4B589F342EC4, 5C742326421B4176855F5E64, 7B6C9A5E9BA84A6BBCBF9B3F, D1A10874BEB246908DF27543); name = MatchLiveTv; sourceTree = "<group>"; };
61A4D30FDB7644FEBBCB6AC7 = {isa = PBXGroup; children = (EA7B4BCC9EAA48CF94ADF09F, 596D2E6129214E95874E4E1F, 82C7043D46EB4FBCB41BFB87, 1F95F2F95BBB45589765B6C1); name = MatchLiveTvTests; sourceTree = "<group>"; };
948F72C223D64471ABFCFE2F = {isa = PBXGroup; children = (9989F3DEB021437FB1D79792, 6F0D757BA4DF4A8BAA37E5A1); name = Products; sourceTree = "<group>"; };
6C16E72ECFC141DABE1BF97D = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (63AA6E164A9D46F08CE75059, 9F4B82FECE1B4479BD5C70EA, 9F22BE9935F84C4F9B6DA971, 6EB025E38C2E45C985C7E315, EE457ABEF9B74DA28A2E2FFD, 2D699C6ABD2840B385194046, 2BF572CC8FBB420CAE651946, 71B88EC9E30A4866B3F7E20E, 0461E337AAD54BBFA537E7E1, 1BFEEBA978A54FC8A1A38D7D, 4A170EAF1F114BA1BD512C77, 8FCD261732D245B69839DAEB, A967247B226E4E86811D86DA, 25360BD296FA4D26AB936847, 77FF5239FE2A458F815117E5, 4CE31F1DC2A141BB97BBBF77, C741D7BBAEF842B1A5CE79F5, 033E9E19FA1D41ECBC6FE954, 9708F347932F4ED9B25F30D4, 6F21F50CE3394618BBDA9DB9, E1EBDFB0867C415FBC6D1267, 43B0ABA8C73547FBBA33F681, 9B29D4ECB04641B386D43176, 9FD1B70E6BC94A5B963C84E3, 53A1BD84C77844F9A5322D71, 55822A3378244F328DEC930B, 8C3A6D2934054AD685C6B17B, BB282D2B90F442FAA19957DE, 12D1F6F6647E4A02B5FAE6A5, 259CD50B331D435CAA934791, 597F0E27F4794C5B8EB4421D, 4CB7FA4311CD496DBAF590D2, 124E11045F024DB79725B158, 00E66D28E2C44685B08D3862, 0D823575D3C34A6AB0B1ADE7, 6826A0C8849B468089F89095, 85DD60C1258B4454879F3958, 33E82511BEF2435A832885B6, 63A3704385234F25B6B4D9DE, DF93C479029448FB98BB26D4, 1E7B2D233349405B9D8850B8, 573B97F0E4D74DCD90962950, 4B7C1844987C4C4A9331EFF6, 5791C4F50F0948E7993907B3, 2E8C344A733B462A87720E91, 2FB16045C6BD4A5E889DD484, 029149C11EA642BEBEA697D8, C9B7DFB707744641BD6CED07, A4D5F108E5374A108B5860B1, F04BF893D3C248C2AF7A546A, 7B19B54BCD0047F980097559, 814C7713F99942A896722EEE, BE9617BC3F2748A2B78AFF9A, 1341F3BB59E24B7DA0B3F492, DA875E6F04BA46C6BB5193DB, B95B2FF16A294D2A82E74BC9, 0AB2C536294B411EA229CFFF, 4EAEC68278E6487496AC81EE, C39AC5E9671941D89416F412, 68C7A87D2C5E49F2A7BCEF99, 85563DC5408842109B436A49, 5DE5E7C500304E16915076E3); runOnlyForDeploymentPostprocessing = 0; };
A4BC052FDBD448CF8DCD2C26 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (0CB7A2DEBA8041A5B75A2F74, B97B7A37B48540A8BB2653A7, 34968BB5B4FD4727A1DB7DA6, 1FB1035FC78C4034BBC93FEF); runOnlyForDeploymentPostprocessing = 0; };
41F620F41CDA460CA5758D12 = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (5A54FCC515114BE19610F99F); runOnlyForDeploymentPostprocessing = 0; };
975D24005603476B9C9AD706 = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
AA380510B8DA455C8ADB600E = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
C30FD711CB14404CA4ACF003 = {isa = XCBuildConfiguration; buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
PRODUCT_NAME = "$(TARGET_NAME)";
API_BASE_URL = "https://www.matchlivetv.it";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
ENABLE_TESTABILITY = YES;
}; name = Debug; };
943E77C5BF47424D930F8289 = {isa = XCBuildConfiguration; buildSettings = {
/* Begin PBXBuildFile section */
020986FB10004F36BB7ED0BD /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806B1034AB5148B4BC55F093 /* TokenStore.swift */; };
0AF514A970C5405EA7A54950 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A245DF805C482C89AEB2E4 /* SplashScreen.swift */; };
100C9225A2EB448C8C3132D4 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F5EB6A380DC4728866BD42A /* LoginScreen.swift */; };
216F56B707484618AE30847E /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A13CA879C5774E6CAA53F3CD /* ScoreRepository.swift */; };
279D1B02380D46DF835831B8 /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5086F0DA44004334881343ED /* TeamColorPicker.swift */; };
3040471A57FA4032BF347025 /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED842B98F33423988B13960 /* OverlayRenderer.swift */; };
3478D3DBB96D41D880BCA499 /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0386B94B9042918A68CD76 /* LivePreviewView.swift */; };
404F2B3994194914994D5998 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F05D132A65E45E8AED152F0 /* ScoreController.swift */; };
40D98FC5AA9B4AB28AC96902 /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7416D8DD1E3D43F2BD1F5863 /* MatchScoringRules.swift */; };
4131A683E5784D51B19916D5 /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B4955A8D804D7BB8235F0F /* ScreenOrientation.swift */; };
45CF302AAFD947ACAD8BDF81 /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3796D03EA5408C8ACB8C3F /* OverlayMappings.swift */; };
45DF0F887F314A498AC82C6D /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B4BBBD7E7D430282730B76 /* WatermarkElement.swift */; };
467525056A0648EDA1E2A0CD /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584D8FC9F6F746E1AE83E96E /* BroadcastControlsOverlay.swift */; };
475FE75875AC47A58609BF5B /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2627CF334644FCA8FD9950B /* WizardComponents.swift */; };
4BDAEE6159624FACA21E178D /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1881B3AF2AC1430B93049FB9 /* BroadcastPermissions.swift */; };
4C5F8E4B2FE308D700962D65 /* HaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = F652BB68B82C49218CCB7CD0 /* HaishinKit */; };
4C5F8E4C2FE308D700962D65 /* RTMPHaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8DDADFA06433457CB69E9BC5 /* RTMPHaishinKit */; };
4C8522208CD14180831F780B /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372F67B9C14F4C7181A18424 /* MatchStatusBadge.swift */; };
4F6973E4E707495FAED4E582 /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316ADC6162224A7E886873E7 /* MatchScreenScaffold.swift */; };
518508A5943C425492A428C5 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8398C3F0CC644579C5CEF35 /* ScoreboardElement.swift */; };
538CA13BDD594A96B444350E /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6077D6649C4FEAA77E083B /* MatchLiveTvApp.swift */; };
572553E2029047128A322BE0 /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7976A76F8D84463BA99F021B /* MatchSecondaryButton.swift */; };
58119B329D05438F84507448 /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE1F75D436643C39DFEE685 /* MatchLiveAPI.swift */; };
587FE4FFD78E472B84911501 /* BroadcastVideoOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0461F47FF06E487491BA53F3 /* BroadcastVideoOrientation.swift */; };
59FAC2E6EDD54B90B41BFD34 /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A0C2A49F46F432BBE5CDE3F /* ApiDtos.swift */; };
65DDC9416BE940A889C8647A /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA2B04BB3A143EC9555D5E5 /* MediaUrl.swift */; };
67BBA82C7CD04A9B85B08F23 /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227E07193A504372A42AA447 /* MatchScoringRulesTests.swift */; };
6868D193A5874D62BD640F8E /* ScoreActionDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F711F4B006CD4A0CAE8788B2 /* ScoreActionDecodeTests.swift */; };
6AF878B33937472981276ED6 /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A84A2EA29C441EFA31498AD /* OverlayState.swift */; };
6C6F8189C8554FA298D0209D /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03F27B7CFAC4095A48C1C83 /* LiveBroadcastEngine.swift */; };
798F944B9FFA401E8EDD6B28 /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F873ACAE570437E9531FCC3 /* WizardSessionHolder.swift */; };
7ACD03602B744B4982DB542C /* ScoreControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F0F380B8FD4B68AD92BD41 /* ScoreControllerTests.swift */; };
82D2A2AC562647C9BD18055D /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0C71AB18CD400E99D2A2E6 /* ColorHex.swift */; };
82D2D8D7BBF642EE8E042CDF /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2222B10172FB4B41AE9E4EEE /* ApiInstantTests.swift */; };
8414C3672E11414CB0F980C3 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26D7D448D0A34D9384C5E2D3 /* Models.swift */; };
85162099CD364680AA080E20 /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86707AFE94F4002BB505F71 /* MatchPrimaryButton.swift */; };
87528ECDBB644E73A6DD4C4B /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8A8C40CEC448A9B49F902A /* OverlayCanvasRenderer.swift */; };
8CF81F29F432430CBD56C405 /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E38BEC61FFD4A0FB010AD13 /* BroadcastModels.swift */; };
906FC2F226DA4F958260FEA0 /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E3A143FFE1C47C9A204B860 /* ActionCableClient.swift */; };
9139292098C24797BE1AFB5E /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10C6CAC995404909A95482BF /* SessionRepository.swift */; };
9C8922FBA5DD4DCA9885E649 /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D813398882A483280580618 /* StepTransmissionScreen.swift */; };
9DA3E777DC3C46CD9F6C2933 /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89921CBC0F64FDEA47164F5 /* DeviceTelemetry.swift */; };
9DA716746DA24ECCA2182B7A /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7DF17673B346B78CA4FBA7 /* LiveBroadcastCoordinator.swift */; };
9E6D271CDEDF4484BAD8161E /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE5A60F7644B4881985D4F15 /* ScoreState.swift */; };
A0B9F06D1A12410E9C1C361D /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE36CEAA140F4953A6872E9C /* OverlayLogoCache.swift */; };
A608CDA9340247E688A315F6 /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC311445F0144B59AE22DA96 /* UserFacingError.swift */; };
A7C1C8C352404A72A688CEF4 /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB19D7F3DBCE427E8FB29C53 /* StepNetworkTestScreen.swift */; };
A80C909B160C46CDADF1ABF8 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E5163A1C8C84E41A5F464F3 /* WizardShellScreen.swift */; };
ACBB5846610943D9AE3FD2B8 /* MatchLiveTv/Resources/Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8E83B664666640A69A448557 /* MatchLiveTv/Resources/Assets.xcassets */; };
B2A84ED6937E4D6780A8372B /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE769961C58E4741B73938B8 /* MatchColors.swift */; };
B469E3D9850E46698740395B /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E221E68194C4C92925A9CB0 /* AppConfig.swift */; };
B54EEA69C2984D0989DDDC3E /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B06452CE3CBF45DE915C5ECB /* CompactScoreboardElement.swift */; };
B6499B988D134CE78DCFF375 /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978A9CB34DF54D4996D6FB3E /* ModalRoutes.swift */; };
BACB4C45854B4531AC2742F3 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC01BBAC06A412296955DB3 /* Routes.swift */; };
C580112F135E4134A55E4DC7 /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976DD89356184E0694B20CB8 /* SessionCableService.swift */; };
CD2D69C03B22451E8E75BEE5 /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6725372B2CE473F87167457 /* ApiInstant.swift */; };
D40F96BF9A98482D861B8714 /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E472FBF3FC74060AB6C7097 /* MatchRepository.swift */; };
D5399E5D54444A43B1E3919B /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DEAC5182ED489798656300 /* AppContainer.swift */; };
D654C3E362EA4786AE9526E5 /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACEF96DCDEA14A7290905C18 /* MatchSessionLauncher.swift */; };
D687E626CEC44A11B2BE78FB /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2962B319CBD24638BB4F3165 /* TeamBrandingEditor.swift */; };
D792D3DE5C984BB880BE19DD /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF97DD369E44828AA6EBF8D /* AppNavHost.swift */; };
D7AAD6BB475B4734BA4EC4EA /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21CD0798A85B4FBAB32957EE /* StepMatchScreen.swift */; };
D81D1E21B0E44F38A8F5C256 /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C8D8270FA44E59A4BD0643 /* MatchesScreen.swift */; };
DAFBA526BBFC4302A851BB93 /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F55DC7DC4764DA99C378C26 /* KeepScreenOn.swift */; };
DC75AFA217B5445AA4FA8BCF /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F780D9DA2C44DDDBF8083ED /* BroadcastScreen.swift */; };
DEE3503ADE4E48F48A49001D /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B453249C904B299178A996 /* AuthRepository.swift */; };
DFEE71197E4E41CBB835D0BA /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944972DE154D444781B612ED /* MatchHubFilter.swift */; };
E382BF9CB4274DDA96A10A98 /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E770890933A84622ABF4EA9F /* LiveScoreActions.swift */; };
F588C2FE35B140418C13963F /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388619A53CF744C6A133A858 /* MatchLiveWordmark.swift */; };
FB14C028958F46EC8A8FAB18 /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43F8C92A1094AC39A7FE978 /* SponsorElement.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
C081F7F08D6748AB8C4B8DFB /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8764A9D2A234F91B52DDC89 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 2F5D6393282640E5A53D4CFC;
remoteInfo = MatchLiveTv;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0461F47FF06E487491BA53F3 /* BroadcastVideoOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastVideoOrientation.swift; path = MatchLiveTv/Streaming/BroadcastVideoOrientation.swift; sourceTree = "<group>"; };
08A245DF805C482C89AEB2E4 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SplashScreen.swift; path = MatchLiveTv/UI/Splash/SplashScreen.swift; sourceTree = "<group>"; };
0A84A2EA29C441EFA31498AD /* OverlayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayState.swift; path = MatchLiveTv/Streaming/Overlay/OverlayState.swift; sourceTree = "<group>"; };
0BE1F75D436643C39DFEE685 /* MatchLiveAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveAPI.swift; path = MatchLiveTv/Data/API/MatchLiveAPI.swift; sourceTree = "<group>"; };
10C6CAC995404909A95482BF /* SessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionRepository.swift; path = MatchLiveTv/Data/Repository/SessionRepository.swift; sourceTree = "<group>"; };
1881B3AF2AC1430B93049FB9 /* BroadcastPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastPermissions.swift; path = MatchLiveTv/UI/Permissions/BroadcastPermissions.swift; sourceTree = "<group>"; };
1ED842B98F33423988B13960 /* OverlayRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayRenderer.swift; sourceTree = "<group>"; };
21CD0798A85B4FBAB32957EE /* StepMatchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepMatchScreen.swift; path = MatchLiveTv/UI/Wizard/StepMatchScreen.swift; sourceTree = "<group>"; };
2222B10172FB4B41AE9E4EEE /* ApiInstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstantTests.swift; path = MatchLiveTvTests/ApiInstantTests.swift; sourceTree = "<group>"; };
227E07193A504372A42AA447 /* MatchScoringRulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRulesTests.swift; path = MatchLiveTvTests/MatchScoringRulesTests.swift; sourceTree = "<group>"; };
26D7D448D0A34D9384C5E2D3 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Models.swift; path = MatchLiveTv/Domain/Models.swift; sourceTree = "<group>"; };
2962B319CBD24638BB4F3165 /* TeamBrandingEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamBrandingEditor.swift; path = MatchLiveTv/UI/Wizard/TeamBrandingEditor.swift; sourceTree = "<group>"; };
2D0386B94B9042918A68CD76 /* LivePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LivePreviewView.swift; path = MatchLiveTv/Streaming/Preview/LivePreviewView.swift; sourceTree = "<group>"; };
2E221E68194C4C92925A9CB0 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfig.swift; path = MatchLiveTv/Core/AppConfig.swift; sourceTree = "<group>"; };
2E5163A1C8C84E41A5F464F3 /* WizardShellScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardShellScreen.swift; path = MatchLiveTv/UI/Wizard/WizardShellScreen.swift; sourceTree = "<group>"; };
2F55DC7DC4764DA99C378C26 /* KeepScreenOn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = KeepScreenOn.swift; path = MatchLiveTv/UI/System/KeepScreenOn.swift; sourceTree = "<group>"; };
316ADC6162224A7E886873E7 /* MatchScreenScaffold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScreenScaffold.swift; path = MatchLiveTv/UI/Components/MatchScreenScaffold.swift; sourceTree = "<group>"; };
34C8D8270FA44E59A4BD0643 /* MatchesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchesScreen.swift; path = MatchLiveTv/UI/Matches/MatchesScreen.swift; sourceTree = "<group>"; };
372F67B9C14F4C7181A18424 /* MatchStatusBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchStatusBadge.swift; path = MatchLiveTv/UI/Components/MatchStatusBadge.swift; sourceTree = "<group>"; };
388619A53CF744C6A133A858 /* MatchLiveWordmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveWordmark.swift; path = MatchLiveTv/UI/Components/MatchLiveWordmark.swift; sourceTree = "<group>"; };
3CF97DD369E44828AA6EBF8D /* AppNavHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppNavHost.swift; path = MatchLiveTv/UI/Navigation/AppNavHost.swift; sourceTree = "<group>"; };
3F5EB6A380DC4728866BD42A /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LoginScreen.swift; path = MatchLiveTv/UI/Login/LoginScreen.swift; sourceTree = "<group>"; };
3F780D9DA2C44DDDBF8083ED /* BroadcastScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastScreen.swift; path = MatchLiveTv/UI/Broadcast/BroadcastScreen.swift; sourceTree = "<group>"; };
4C0C71AB18CD400E99D2A2E6 /* ColorHex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ColorHex.swift; path = MatchLiveTv/Core/ColorHex.swift; sourceTree = "<group>"; };
4C3796D03EA5408C8ACB8C3F /* OverlayMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayMappings.swift; path = MatchLiveTv/Streaming/Overlay/OverlayMappings.swift; sourceTree = "<group>"; };
4E38BEC61FFD4A0FB010AD13 /* BroadcastModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastModels.swift; path = MatchLiveTv/Streaming/BroadcastModels.swift; sourceTree = "<group>"; };
4F873ACAE570437E9531FCC3 /* WizardSessionHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardSessionHolder.swift; path = MatchLiveTv/Data/WizardSessionHolder.swift; sourceTree = "<group>"; };
5086F0DA44004334881343ED /* TeamColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TeamColorPicker.swift; path = MatchLiveTv/UI/Wizard/TeamColorPicker.swift; sourceTree = "<group>"; };
551CCAA3C2F64172A59E278B /* MatchLiveTv/Resources/Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = MatchLiveTv/Resources/Info.plist; sourceTree = "<group>"; };
584D8FC9F6F746E1AE83E96E /* BroadcastControlsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BroadcastControlsOverlay.swift; path = MatchLiveTv/UI/Broadcast/BroadcastControlsOverlay.swift; sourceTree = "<group>"; };
5D813398882A483280580618 /* StepTransmissionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepTransmissionScreen.swift; path = MatchLiveTv/UI/Wizard/StepTransmissionScreen.swift; sourceTree = "<group>"; };
6E7DF17673B346B78CA4FBA7 /* LiveBroadcastCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastCoordinator.swift; path = MatchLiveTv/Streaming/LiveBroadcastCoordinator.swift; sourceTree = "<group>"; };
6F05D132A65E45E8AED152F0 /* ScoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreController.swift; path = MatchLiveTv/Data/Scoring/ScoreController.swift; sourceTree = "<group>"; };
7416D8DD1E3D43F2BD1F5863 /* MatchScoringRules.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchScoringRules.swift; path = MatchLiveTv/Domain/MatchScoringRules.swift; sourceTree = "<group>"; };
7976A76F8D84463BA99F021B /* MatchSecondaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSecondaryButton.swift; path = MatchLiveTv/UI/Components/MatchSecondaryButton.swift; sourceTree = "<group>"; };
7E472FBF3FC74060AB6C7097 /* MatchRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchRepository.swift; path = MatchLiveTv/Data/Repository/MatchRepository.swift; sourceTree = "<group>"; };
806B1034AB5148B4BC55F093 /* TokenStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TokenStore.swift; path = MatchLiveTv/Core/TokenStore.swift; sourceTree = "<group>"; };
8A0C2A49F46F432BBE5CDE3F /* ApiDtos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiDtos.swift; path = MatchLiveTv/Data/API/ApiDtos.swift; sourceTree = "<group>"; };
8E83B664666640A69A448557 /* MatchLiveTv/Resources/Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = MatchLiveTv/Resources/Assets.xcassets; sourceTree = "<group>"; };
8FA2B04BB3A143EC9555D5E5 /* MediaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaUrl.swift; path = MatchLiveTv/Core/MediaUrl.swift; sourceTree = "<group>"; };
944972DE154D444781B612ED /* MatchHubFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchHubFilter.swift; path = MatchLiveTv/Domain/MatchHubFilter.swift; sourceTree = "<group>"; };
94F0F380B8FD4B68AD92BD41 /* ScoreControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreControllerTests.swift; path = MatchLiveTvTests/ScoreControllerTests.swift; sourceTree = "<group>"; };
96B4BBBD7E7D430282730B76 /* WatermarkElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WatermarkElement.swift; path = MatchLiveTv/Streaming/Overlay/WatermarkElement.swift; sourceTree = "<group>"; };
976DD89356184E0694B20CB8 /* SessionCableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SessionCableService.swift; path = MatchLiveTv/Data/Cable/SessionCableService.swift; sourceTree = "<group>"; };
978A9CB34DF54D4996D6FB3E /* ModalRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ModalRoutes.swift; path = MatchLiveTv/UI/Navigation/ModalRoutes.swift; sourceTree = "<group>"; };
9E3A143FFE1C47C9A204B860 /* ActionCableClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ActionCableClient.swift; path = MatchLiveTv/Data/Cable/ActionCableClient.swift; sourceTree = "<group>"; };
A13CA879C5774E6CAA53F3CD /* ScoreRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreRepository.swift; path = MatchLiveTv/Data/Repository/ScoreRepository.swift; sourceTree = "<group>"; };
A2627CF334644FCA8FD9950B /* WizardComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WizardComponents.swift; path = MatchLiveTv/UI/Wizard/WizardComponents.swift; sourceTree = "<group>"; };
A86707AFE94F4002BB505F71 /* MatchPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchPrimaryButton.swift; path = MatchLiveTv/UI/Components/MatchPrimaryButton.swift; sourceTree = "<group>"; };
ACEF96DCDEA14A7290905C18 /* MatchSessionLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchSessionLauncher.swift; path = MatchLiveTv/Data/Repository/MatchSessionLauncher.swift; sourceTree = "<group>"; };
AE769961C58E4741B73938B8 /* MatchColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchColors.swift; path = MatchLiveTv/UI/Theme/MatchColors.swift; sourceTree = "<group>"; };
B06452CE3CBF45DE915C5ECB /* CompactScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CompactScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/CompactScoreboardElement.swift; sourceTree = "<group>"; };
BA8A8C40CEC448A9B49F902A /* OverlayCanvasRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayCanvasRenderer.swift; path = MatchLiveTv/Streaming/Overlay/OverlayCanvasRenderer.swift; sourceTree = "<group>"; };
C1B4955A8D804D7BB8235F0F /* ScreenOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScreenOrientation.swift; path = MatchLiveTv/UI/System/ScreenOrientation.swift; sourceTree = "<group>"; };
C3DCC59301E94417BC24492F /* MatchLiveTv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatchLiveTv.app; sourceTree = BUILT_PRODUCTS_DIR; };
C43F8C92A1094AC39A7FE978 /* SponsorElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SponsorElement.swift; path = MatchLiveTv/Streaming/Overlay/SponsorElement.swift; sourceTree = "<group>"; };
CCC01BBAC06A412296955DB3 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Routes.swift; path = MatchLiveTv/UI/Navigation/Routes.swift; sourceTree = "<group>"; };
CE36CEAA140F4953A6872E9C /* OverlayLogoCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OverlayLogoCache.swift; path = MatchLiveTv/Streaming/Overlay/OverlayLogoCache.swift; sourceTree = "<group>"; };
D6725372B2CE473F87167457 /* ApiInstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ApiInstant.swift; path = MatchLiveTv/Core/ApiInstant.swift; sourceTree = "<group>"; };
D6B453249C904B299178A996 /* AuthRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AuthRepository.swift; path = MatchLiveTv/Data/Repository/AuthRepository.swift; sourceTree = "<group>"; };
D8398C3F0CC644579C5CEF35 /* ScoreboardElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreboardElement.swift; path = MatchLiveTv/Streaming/Overlay/ScoreboardElement.swift; sourceTree = "<group>"; };
DE5A60F7644B4881985D4F15 /* ScoreState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreState.swift; path = MatchLiveTv/Domain/ScoreState.swift; sourceTree = "<group>"; };
E3DEAC5182ED489798656300 /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppContainer.swift; path = MatchLiveTv/Data/AppContainer.swift; sourceTree = "<group>"; };
E770890933A84622ABF4EA9F /* LiveScoreActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveScoreActions.swift; path = MatchLiveTv/UI/Broadcast/LiveScoreActions.swift; sourceTree = "<group>"; };
EA6077D6649C4FEAA77E083B /* MatchLiveTvApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MatchLiveTvApp.swift; path = MatchLiveTv/App/MatchLiveTvApp.swift; sourceTree = "<group>"; };
EC311445F0144B59AE22DA96 /* UserFacingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserFacingError.swift; path = MatchLiveTv/Core/UserFacingError.swift; sourceTree = "<group>"; };
ED0935DD32EB4B28AA67CB67 /* MatchLiveTvTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatchLiveTvTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F03F27B7CFAC4095A48C1C83 /* LiveBroadcastEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveBroadcastEngine.swift; path = MatchLiveTv/Streaming/LiveBroadcastEngine.swift; sourceTree = "<group>"; };
F711F4B006CD4A0CAE8788B2 /* ScoreActionDecodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScoreActionDecodeTests.swift; path = MatchLiveTvTests/ScoreActionDecodeTests.swift; sourceTree = "<group>"; };
F89921CBC0F64FDEA47164F5 /* DeviceTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DeviceTelemetry.swift; path = MatchLiveTv/Core/DeviceTelemetry.swift; sourceTree = "<group>"; };
FB19D7F3DBCE427E8FB29C53 /* StepNetworkTestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StepNetworkTestScreen.swift; path = MatchLiveTv/UI/Wizard/StepNetworkTestScreen.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
02B7024F8C4E4D41AE3B203A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
4CD553EA1678427BB1BB0478 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4C5F8E4C2FE308D700962D65 /* RTMPHaishinKit in Frameworks */,
4C5F8E4B2FE308D700962D65 /* HaishinKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
146562E9E8F4444781CCEE1C /* MatchLiveTv */ = {
isa = PBXGroup;
children = (
EA6077D6649C4FEAA77E083B /* MatchLiveTvApp.swift */,
D6725372B2CE473F87167457 /* ApiInstant.swift */,
2E221E68194C4C92925A9CB0 /* AppConfig.swift */,
4C0C71AB18CD400E99D2A2E6 /* ColorHex.swift */,
F89921CBC0F64FDEA47164F5 /* DeviceTelemetry.swift */,
8FA2B04BB3A143EC9555D5E5 /* MediaUrl.swift */,
806B1034AB5148B4BC55F093 /* TokenStore.swift */,
EC311445F0144B59AE22DA96 /* UserFacingError.swift */,
8A0C2A49F46F432BBE5CDE3F /* ApiDtos.swift */,
0BE1F75D436643C39DFEE685 /* MatchLiveAPI.swift */,
E3DEAC5182ED489798656300 /* AppContainer.swift */,
9E3A143FFE1C47C9A204B860 /* ActionCableClient.swift */,
976DD89356184E0694B20CB8 /* SessionCableService.swift */,
D6B453249C904B299178A996 /* AuthRepository.swift */,
7E472FBF3FC74060AB6C7097 /* MatchRepository.swift */,
ACEF96DCDEA14A7290905C18 /* MatchSessionLauncher.swift */,
A13CA879C5774E6CAA53F3CD /* ScoreRepository.swift */,
10C6CAC995404909A95482BF /* SessionRepository.swift */,
6F05D132A65E45E8AED152F0 /* ScoreController.swift */,
4F873ACAE570437E9531FCC3 /* WizardSessionHolder.swift */,
944972DE154D444781B612ED /* MatchHubFilter.swift */,
7416D8DD1E3D43F2BD1F5863 /* MatchScoringRules.swift */,
26D7D448D0A34D9384C5E2D3 /* Models.swift */,
DE5A60F7644B4881985D4F15 /* ScoreState.swift */,
4E38BEC61FFD4A0FB010AD13 /* BroadcastModels.swift */,
0461F47FF06E487491BA53F3 /* BroadcastVideoOrientation.swift */,
6E7DF17673B346B78CA4FBA7 /* LiveBroadcastCoordinator.swift */,
F03F27B7CFAC4095A48C1C83 /* LiveBroadcastEngine.swift */,
B06452CE3CBF45DE915C5ECB /* CompactScoreboardElement.swift */,
BA8A8C40CEC448A9B49F902A /* OverlayCanvasRenderer.swift */,
CE36CEAA140F4953A6872E9C /* OverlayLogoCache.swift */,
4C3796D03EA5408C8ACB8C3F /* OverlayMappings.swift */,
1ED842B98F33423988B13960 /* OverlayRenderer.swift */,
0A84A2EA29C441EFA31498AD /* OverlayState.swift */,
D8398C3F0CC644579C5CEF35 /* ScoreboardElement.swift */,
C43F8C92A1094AC39A7FE978 /* SponsorElement.swift */,
96B4BBBD7E7D430282730B76 /* WatermarkElement.swift */,
2D0386B94B9042918A68CD76 /* LivePreviewView.swift */,
584D8FC9F6F746E1AE83E96E /* BroadcastControlsOverlay.swift */,
3F780D9DA2C44DDDBF8083ED /* BroadcastScreen.swift */,
E770890933A84622ABF4EA9F /* LiveScoreActions.swift */,
388619A53CF744C6A133A858 /* MatchLiveWordmark.swift */,
A86707AFE94F4002BB505F71 /* MatchPrimaryButton.swift */,
316ADC6162224A7E886873E7 /* MatchScreenScaffold.swift */,
7976A76F8D84463BA99F021B /* MatchSecondaryButton.swift */,
372F67B9C14F4C7181A18424 /* MatchStatusBadge.swift */,
3F5EB6A380DC4728866BD42A /* LoginScreen.swift */,
34C8D8270FA44E59A4BD0643 /* MatchesScreen.swift */,
3CF97DD369E44828AA6EBF8D /* AppNavHost.swift */,
978A9CB34DF54D4996D6FB3E /* ModalRoutes.swift */,
CCC01BBAC06A412296955DB3 /* Routes.swift */,
1881B3AF2AC1430B93049FB9 /* BroadcastPermissions.swift */,
08A245DF805C482C89AEB2E4 /* SplashScreen.swift */,
2F55DC7DC4764DA99C378C26 /* KeepScreenOn.swift */,
C1B4955A8D804D7BB8235F0F /* ScreenOrientation.swift */,
AE769961C58E4741B73938B8 /* MatchColors.swift */,
21CD0798A85B4FBAB32957EE /* StepMatchScreen.swift */,
FB19D7F3DBCE427E8FB29C53 /* StepNetworkTestScreen.swift */,
5D813398882A483280580618 /* StepTransmissionScreen.swift */,
2962B319CBD24638BB4F3165 /* TeamBrandingEditor.swift */,
5086F0DA44004334881343ED /* TeamColorPicker.swift */,
A2627CF334644FCA8FD9950B /* WizardComponents.swift */,
2E5163A1C8C84E41A5F464F3 /* WizardShellScreen.swift */,
8E83B664666640A69A448557 /* MatchLiveTv/Resources/Assets.xcassets */,
551CCAA3C2F64172A59E278B /* MatchLiveTv/Resources/Info.plist */,
);
name = MatchLiveTv;
sourceTree = "<group>";
};
25CDF42A0993431FA5F457EB = {
isa = PBXGroup;
children = (
146562E9E8F4444781CCEE1C /* MatchLiveTv */,
4190C9EAA59D4AA882933799 /* MatchLiveTvTests */,
B06BE907209A475AA6898BB6 /* Products */,
);
sourceTree = "<group>";
};
4190C9EAA59D4AA882933799 /* MatchLiveTvTests */ = {
isa = PBXGroup;
children = (
2222B10172FB4B41AE9E4EEE /* ApiInstantTests.swift */,
227E07193A504372A42AA447 /* MatchScoringRulesTests.swift */,
F711F4B006CD4A0CAE8788B2 /* ScoreActionDecodeTests.swift */,
94F0F380B8FD4B68AD92BD41 /* ScoreControllerTests.swift */,
);
name = MatchLiveTvTests;
sourceTree = "<group>";
};
B06BE907209A475AA6898BB6 /* Products */ = {
isa = PBXGroup;
children = (
C3DCC59301E94417BC24492F /* MatchLiveTv.app */,
ED0935DD32EB4B28AA67CB67 /* MatchLiveTvTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
2F5D6393282640E5A53D4CFC /* MatchLiveTv */ = {
isa = PBXNativeTarget;
buildConfigurationList = 56CDE22EB06747D988C0F28D /* Build configuration list for PBXNativeTarget "MatchLiveTv" */;
buildPhases = (
A547C0346D1345579D28B86D /* Sources */,
4CD553EA1678427BB1BB0478 /* Frameworks */,
CA0D00DDC25242438A7EBFEC /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = MatchLiveTv;
packageProductDependencies = (
F652BB68B82C49218CCB7CD0 /* HaishinKit */,
8DDADFA06433457CB69E9BC5 /* RTMPHaishinKit */,
);
productName = MatchLiveTv;
productReference = C3DCC59301E94417BC24492F /* MatchLiveTv.app */;
productType = "com.apple.product-type.application";
};
938E427E770D4DFD8E992BFA /* MatchLiveTvTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7CCBB8C54461461C9BF93E4A /* Build configuration list for PBXNativeTarget "MatchLiveTvTests" */;
buildPhases = (
F741ACE11A14421D99BBA54D /* Sources */,
02B7024F8C4E4D41AE3B203A /* Frameworks */,
);
buildRules = (
);
dependencies = (
0831318C61FF46EDA63542ED /* PBXTargetDependency */,
);
name = MatchLiveTvTests;
productName = MatchLiveTvTests;
productReference = ED0935DD32EB4B28AA67CB67 /* MatchLiveTvTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
A8764A9D2A234F91B52DDC89 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 0;
LastSwiftUpdateCheck = 1600;
};
buildConfigurationList = 1B3B522758634988987B6EE5 /* Build configuration list for PBXProject "MatchLiveTv" */;
compatibilityVersion = "Xcode 15.0";
developmentRegion = it;
hasScannedForEncodings = 0;
knownRegions = (
it,
en,
Base,
);
mainGroup = 25CDF42A0993431FA5F457EB;
packageReferences = (
9BBC13CDC0274B3EA6A38447 /* XCRemoteSwiftPackageReference "HaishinKit" */,
);
productRefGroup = B06BE907209A475AA6898BB6 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
2F5D6393282640E5A53D4CFC /* MatchLiveTv */,
938E427E770D4DFD8E992BFA /* MatchLiveTvTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
CA0D00DDC25242438A7EBFEC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
ACBB5846610943D9AE3FD2B8 /* MatchLiveTv/Resources/Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
A547C0346D1345579D28B86D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
538CA13BDD594A96B444350E /* MatchLiveTvApp.swift in Sources */,
CD2D69C03B22451E8E75BEE5 /* ApiInstant.swift in Sources */,
B469E3D9850E46698740395B /* AppConfig.swift in Sources */,
82D2A2AC562647C9BD18055D /* ColorHex.swift in Sources */,
9DA3E777DC3C46CD9F6C2933 /* DeviceTelemetry.swift in Sources */,
65DDC9416BE940A889C8647A /* MediaUrl.swift in Sources */,
020986FB10004F36BB7ED0BD /* TokenStore.swift in Sources */,
A608CDA9340247E688A315F6 /* UserFacingError.swift in Sources */,
59FAC2E6EDD54B90B41BFD34 /* ApiDtos.swift in Sources */,
58119B329D05438F84507448 /* MatchLiveAPI.swift in Sources */,
D5399E5D54444A43B1E3919B /* AppContainer.swift in Sources */,
906FC2F226DA4F958260FEA0 /* ActionCableClient.swift in Sources */,
C580112F135E4134A55E4DC7 /* SessionCableService.swift in Sources */,
DEE3503ADE4E48F48A49001D /* AuthRepository.swift in Sources */,
D40F96BF9A98482D861B8714 /* MatchRepository.swift in Sources */,
D654C3E362EA4786AE9526E5 /* MatchSessionLauncher.swift in Sources */,
216F56B707484618AE30847E /* ScoreRepository.swift in Sources */,
9139292098C24797BE1AFB5E /* SessionRepository.swift in Sources */,
404F2B3994194914994D5998 /* ScoreController.swift in Sources */,
798F944B9FFA401E8EDD6B28 /* WizardSessionHolder.swift in Sources */,
DFEE71197E4E41CBB835D0BA /* MatchHubFilter.swift in Sources */,
40D98FC5AA9B4AB28AC96902 /* MatchScoringRules.swift in Sources */,
8414C3672E11414CB0F980C3 /* Models.swift in Sources */,
9E6D271CDEDF4484BAD8161E /* ScoreState.swift in Sources */,
8CF81F29F432430CBD56C405 /* BroadcastModels.swift in Sources */,
587FE4FFD78E472B84911501 /* BroadcastVideoOrientation.swift in Sources */,
9DA716746DA24ECCA2182B7A /* LiveBroadcastCoordinator.swift in Sources */,
6C6F8189C8554FA298D0209D /* LiveBroadcastEngine.swift in Sources */,
B54EEA69C2984D0989DDDC3E /* CompactScoreboardElement.swift in Sources */,
87528ECDBB644E73A6DD4C4B /* OverlayCanvasRenderer.swift in Sources */,
A0B9F06D1A12410E9C1C361D /* OverlayLogoCache.swift in Sources */,
45CF302AAFD947ACAD8BDF81 /* OverlayMappings.swift in Sources */,
3040471A57FA4032BF347025 /* OverlayRenderer.swift in Sources */,
6AF878B33937472981276ED6 /* OverlayState.swift in Sources */,
518508A5943C425492A428C5 /* ScoreboardElement.swift in Sources */,
FB14C028958F46EC8A8FAB18 /* SponsorElement.swift in Sources */,
45DF0F887F314A498AC82C6D /* WatermarkElement.swift in Sources */,
3478D3DBB96D41D880BCA499 /* LivePreviewView.swift in Sources */,
467525056A0648EDA1E2A0CD /* BroadcastControlsOverlay.swift in Sources */,
DC75AFA217B5445AA4FA8BCF /* BroadcastScreen.swift in Sources */,
E382BF9CB4274DDA96A10A98 /* LiveScoreActions.swift in Sources */,
F588C2FE35B140418C13963F /* MatchLiveWordmark.swift in Sources */,
85162099CD364680AA080E20 /* MatchPrimaryButton.swift in Sources */,
4F6973E4E707495FAED4E582 /* MatchScreenScaffold.swift in Sources */,
572553E2029047128A322BE0 /* MatchSecondaryButton.swift in Sources */,
4C8522208CD14180831F780B /* MatchStatusBadge.swift in Sources */,
100C9225A2EB448C8C3132D4 /* LoginScreen.swift in Sources */,
D81D1E21B0E44F38A8F5C256 /* MatchesScreen.swift in Sources */,
D792D3DE5C984BB880BE19DD /* AppNavHost.swift in Sources */,
B6499B988D134CE78DCFF375 /* ModalRoutes.swift in Sources */,
BACB4C45854B4531AC2742F3 /* Routes.swift in Sources */,
4BDAEE6159624FACA21E178D /* BroadcastPermissions.swift in Sources */,
0AF514A970C5405EA7A54950 /* SplashScreen.swift in Sources */,
DAFBA526BBFC4302A851BB93 /* KeepScreenOn.swift in Sources */,
4131A683E5784D51B19916D5 /* ScreenOrientation.swift in Sources */,
B2A84ED6937E4D6780A8372B /* MatchColors.swift in Sources */,
D7AAD6BB475B4734BA4EC4EA /* StepMatchScreen.swift in Sources */,
A7C1C8C352404A72A688CEF4 /* StepNetworkTestScreen.swift in Sources */,
9C8922FBA5DD4DCA9885E649 /* StepTransmissionScreen.swift in Sources */,
D687E626CEC44A11B2BE78FB /* TeamBrandingEditor.swift in Sources */,
279D1B02380D46DF835831B8 /* TeamColorPicker.swift in Sources */,
475FE75875AC47A58609BF5B /* WizardComponents.swift in Sources */,
A80C909B160C46CDADF1ABF8 /* WizardShellScreen.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F741ACE11A14421D99BBA54D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
82D2D8D7BBF642EE8E042CDF /* ApiInstantTests.swift in Sources */,
67BBA82C7CD04A9B85B08F23 /* MatchScoringRulesTests.swift in Sources */,
6868D193A5874D62BD640F8E /* ScoreActionDecodeTests.swift in Sources */,
7ACD03602B744B4982DB542C /* ScoreControllerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
0831318C61FF46EDA63542ED /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 2F5D6393282640E5A53D4CFC /* MatchLiveTv */;
targetProxy = C081F7F08D6748AB8C4B8DFB /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
5C13053150F24474B8F31912 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
SWIFT_VERSION = 5.0;
};
name = Release;
};
5EAD837968924A88BDE4D77F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
API_BASE_URL = "https://www.matchlivetv.it";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv;
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
PRODUCT_NAME = "$(TARGET_NAME)";
API_BASE_URL = "https://www.matchlivetv.it";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
}; name = Release; };
490BD52EC789443397269936 = {isa = XCBuildConfiguration; buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
}; name = Debug; };
EC6EAD89012E49BA855E8B4C = {isa = XCBuildConfiguration; buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.match-live-tv.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
}; name = Release; };
C496B00D0FBC453081E8D965 = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Debug; };
33B07D34DCE74B519F4207EB = {isa = XCBuildConfiguration; buildSettings = {IPHONEOS_DEPLOYMENT_TARGET = 16.0; SWIFT_VERSION = 5.0; }; name = Release; };
95F893DE8F6A4FFBB614AE96 = {isa = XCConfigurationList; buildConfigurations = (C30FD711CB14404CA4ACF003, 943E77C5BF47424D930F8289); defaultConfigurationName = Release; };
2591CEDFD4D6441784591FCE = {isa = XCConfigurationList; buildConfigurations = (490BD52EC789443397269936, EC6EAD89012E49BA855E8B4C); defaultConfigurationName = Release; };
5F49CA34489E4C7F89ECD863 = {isa = XCConfigurationList; buildConfigurations = (C496B00D0FBC453081E8D965, 33B07D34DCE74B519F4207EB); defaultConfigurationName = Release; };
98BE277FE859401F8A05312C = {isa = PBXNativeTarget; buildConfigurationList = 95F893DE8F6A4FFBB614AE96; buildPhases = (6C16E72ECFC141DABE1BF97D, 975D24005603476B9C9AD706, 41F620F41CDA460CA5758D12); buildRules = (); dependencies = (); name = MatchLiveTv; packageProductDependencies = (1794E4AFEC524A2391E1F43A, 5A2B53779BB342F0A4EB1D50); productName = MatchLiveTv; productReference = 9989F3DEB021437FB1D79792; productType = "com.apple.product-type.application"; };
BA0168680D5443B8A9521C5B = {isa = PBXContainerItemProxy; containerPortal = FF548150A3A24533BC87A2B6 /* Project object */; proxyType = 1; remoteGlobalIDString = 98BE277FE859401F8A05312C; remoteInfo = MatchLiveTv; };
E8409388B8964E7B848E487D = {isa = PBXTargetDependency; target = 98BE277FE859401F8A05312C /* MatchLiveTv */; targetProxy = BA0168680D5443B8A9521C5B /* PBXContainerItemProxy */; };
AF6564A25AD445BE97A401CC = {isa = PBXNativeTarget; buildConfigurationList = 2591CEDFD4D6441784591FCE; buildPhases = (A4BC052FDBD448CF8DCD2C26, AA380510B8DA455C8ADB600E); buildRules = (); dependencies = (E8409388B8964E7B848E487D); name = MatchLiveTvTests; productName = MatchLiveTvTests; productReference = 6F0D757BA4DF4A8BAA37E5A1; productType = "com.apple.product-type.bundle.unit-test"; };
144AB99B1DAD4D48925A7103 = {isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/shogo4405/HaishinKit.swift"; requirement = {kind = upToNextMajorVersion; minimumVersion = 2.0.0;}; };
FF548150A3A24533BC87A2B6 = {isa = PBXProject; attributes = {BuildIndependentTargetsInParallel = 0; LastSwiftUpdateCheck = 1600;}; buildConfigurationList = 5F49CA34489E4C7F89ECD863; compatibilityVersion = "Xcode 15.0"; developmentRegion = it; mainGroup = 9E10ADDF29394FD6B200D2A7; packageReferences = (144AB99B1DAD4D48925A7103); productRefGroup = 948F72C223D64471ABFCFE2F; targets = (98BE277FE859401F8A05312C, AF6564A25AD445BE97A401CC); };
};
rootObject = FF548150A3A24533BC87A2B6 /* Project object */;
name = Release;
};
978E94D1AF904CFE86165956 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
};
name = Debug;
};
9A8D41AAA16F4A448DFA4ED9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
API_BASE_URL = "https://www.matchlivetv.it";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
DEVELOPMENT_TEAM = S8Q9TWBRG5;
ENABLE_TESTABILITY = YES;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
B9E60323FF35415C9388228C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
CB913E17F096470F990A496F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 21;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.matchlivetv.match-live-tv.tests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatchLiveTv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MatchLiveTv";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1B3B522758634988987B6EE5 /* Build configuration list for PBXProject "MatchLiveTv" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B9E60323FF35415C9388228C /* Debug */,
5C13053150F24474B8F31912 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
56CDE22EB06747D988C0F28D /* Build configuration list for PBXNativeTarget "MatchLiveTv" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9A8D41AAA16F4A448DFA4ED9 /* Debug */,
5EAD837968924A88BDE4D77F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7CCBB8C54461461C9BF93E4A /* Build configuration list for PBXNativeTarget "MatchLiveTvTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
978E94D1AF904CFE86165956 /* Debug */,
CB913E17F096470F990A496F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
9BBC13CDC0274B3EA6A38447 /* XCRemoteSwiftPackageReference "HaishinKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/shogo4405/HaishinKit.swift";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
8DDADFA06433457CB69E9BC5 /* RTMPHaishinKit */ = {
isa = XCSwiftPackageProductDependency;
package = 9BBC13CDC0274B3EA6A38447 /* XCRemoteSwiftPackageReference "HaishinKit" */;
productName = RTMPHaishinKit;
};
F652BB68B82C49218CCB7CD0 /* HaishinKit */ = {
isa = XCSwiftPackageProductDependency;
package = 9BBC13CDC0274B3EA6A38447 /* XCRemoteSwiftPackageReference "HaishinKit" */;
productName = HaishinKit;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = A8764A9D2A234F91B52DDC89 /* Project object */;
}

View File

@@ -3,28 +3,28 @@
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES">
<BuildActionEntries>
<BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="98BE277FE859401F8A05312C" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="2F5D6393282640E5A53D4CFC" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
</BuildActionEntry>
<BuildActionEntry buildForTesting="YES" buildForRunning="NO" buildForProfiling="NO" buildForArchiving="NO" buildForAnalyzing="NO">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="AF6564A25AD445BE97A401CC" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="938E427E770D4DFD8E992BFA" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv="YES">
<Testables>
<TestableReference skipped="NO">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="AF6564A25AD445BE97A401CC" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="938E427E770D4DFD8E992BFA" BuildableName="MatchLiveTvTests.xctest" BlueprintName="MatchLiveTvTests" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" debugServiceExtension="internal" allowLocationSimulation="YES">
<BuildableProductRunnable runnableDebuggingMode="0">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="98BE277FE859401F8A05312C" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="2F5D6393282640E5A53D4CFC" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES">
<BuildableProductRunnable runnableDebuggingMode="0">
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="98BE277FE859401F8A05312C" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="2F5D6393282640E5A53D4CFC" BuildableName="MatchLiveTv.app" BlueprintName="MatchLiveTv" ReferencedContainer="container:MatchLiveTv.xcodeproj"/>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction buildConfiguration="Debug"/>

View File

@@ -7,6 +7,10 @@ struct LoginRequest: Encodable {
struct RefreshRequest: Encodable {
let refreshToken: String
enum CodingKeys: String, CodingKey {
case refreshToken = "refresh_token"
}
}
struct LoginResponse: Decodable {

View File

@@ -43,6 +43,8 @@ final class MatchLiveAPI: @unchecked Sendable {
private let session: URLSession
private var accessToken: String?
private let baseURL: URL
/// Chiamato su 401 per rinnovare il token e ritentare la richiesta (una volta).
var tokenRefresher: (() async throws -> String?)?
init(baseURL: URL = URL(string: AppConfig.apiV1)!, session: URLSession = .shared) {
self.baseURL = baseURL
@@ -50,7 +52,7 @@ final class MatchLiveAPI: @unchecked Sendable {
}
func setAccessToken(_ token: String?) {
accessToken = token
accessToken = token?.trimmingCharacters(in: .whitespacesAndNewlines)
}
// MARK: - Auth
@@ -232,10 +234,20 @@ final class MatchLiveAPI: @unchecked Sendable {
return try await execute(request)
}
private func execute<T: Decodable>(_ request: URLRequest) async throws -> T {
private func execute<T: Decodable>(_ request: URLRequest, canRefresh: Bool = true) async throws -> T {
let (data, response) = try await session.data(for: request)
guard let http = response as? HTTPURLResponse else { throw APIError.invalidURL }
if http.statusCode == 401 { throw APIError.unauthorized }
if http.statusCode == 401 {
if canRefresh, !isPublicAuthRequest(request), let tokenRefresher {
if let newToken = try await tokenRefresher() {
accessToken = newToken
var retry = request
retry.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization")
return try await execute(retry, canRefresh: false)
}
}
throw APIError.unauthorized
}
guard (200..<300).contains(http.statusCode) else {
let body = String(data: data, encoding: .utf8)
throw APIError.http(http.statusCode, body)
@@ -249,6 +261,13 @@ final class MatchLiveAPI: @unchecked Sendable {
throw APIError.decoding(error)
}
}
private func isPublicAuthRequest(_ request: URLRequest) -> Bool {
guard let path = request.url?.path else { return false }
return path.hasSuffix("/auth/login")
|| path.hasSuffix("/auth/refresh")
|| path.hasSuffix("/auth/register")
}
}
private struct EmptyResponse: Decodable {}

View File

@@ -18,10 +18,15 @@ final class AppContainer: ObservableObject {
let tokenStore = TokenStore()
self.api = api
self.tokenStore = tokenStore
authRepository = AuthRepository(api: api, tokenStore: tokenStore) { token in
let authRepository = AuthRepository(api: api, tokenStore: tokenStore) { token in
api.setAccessToken(token)
OverlayLogoCache.configure(token: token)
}
self.authRepository = authRepository
api.tokenRefresher = {
guard let stored = authRepository.currentSession else { return nil }
return try await authRepository.refresh(stored.refreshToken).accessToken
}
matchRepository = MatchRepository(api: api, tokenStore: tokenStore)
sessionRepository = SessionRepository(api: api)
scoreRepository = ScoreRepository(api: api)

View File

@@ -28,9 +28,14 @@ final class AuthRepository {
do {
_ = try await api.me()
return stored
} catch {
} catch let error as APIError {
if case .unauthorized = error {
return try? await refresh(stored.refreshToken)
}
return stored
} catch {
return stored
}
}
func refresh(_ refreshToken: String) async throws -> StoredSession {

View File

@@ -0,0 +1,31 @@
import AVFoundation
import UIKit
/// Allinea l'orientamento del sensore camera all'interfaccia (broadcast sempre landscape).
enum BroadcastVideoOrientation {
@MainActor
static func captureOrientation() -> AVCaptureVideoOrientation {
if let scene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }) {
return captureOrientation(for: scene.interfaceOrientation)
}
return .landscapeRight
}
@MainActor
static var isPortraitContent: Bool {
let orientation = captureOrientation()
return orientation == .portrait || orientation == .portraitUpsideDown
}
private static func captureOrientation(for orientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation {
switch orientation {
case .portrait: return .portrait
case .portraitUpsideDown: return .portraitUpsideDown
case .landscapeLeft: return .landscapeLeft
case .landscapeRight: return .landscapeRight
default: return .landscapeRight
}
}
}

View File

@@ -3,23 +3,20 @@ import Foundation
import HaishinKit
import RTMPHaishinKit
import UIKit
import VideoToolbox
struct RTMPPublishTarget: Sendable {
let connectURL: String
let streamName: String
private struct BroadcastBitrateMonitor: StreamBitRateStrategy {
let mamimumVideoBitRate: Int = 0
let mamimumAudioBitRate: Int = 0
let onUpdate: @Sendable (Int) -> Void
func adjustBitrate(_ event: NetworkMonitorEvent, stream: some StreamConvertible) async {
switch event {
case .status(let report), .publishInsufficientBWOccured(let report):
onUpdate(max(0, report.currentBytesOutPerSecond / 1024))
case .reset:
break
}
enum RTMPUrlParser {
static func parse(_ urlString: String) -> RTMPPublishTarget? {
guard let url = URL(string: urlString), url.scheme?.hasPrefix("rtmp") == true else { return nil }
let pathParts = url.path.split(separator: "/").map(String.init)
guard pathParts.count >= 2 else { return nil }
let app = pathParts.dropLast().joined(separator: "/")
let streamName = pathParts.last ?? ""
var connect = "rtmp://\(url.host ?? "")"
if let port = url.port { connect += ":\(port)" }
connect += "/\(app)"
return RTMPPublishTarget(connectURL: connect, streamName: streamName)
}
}
@@ -28,20 +25,27 @@ final class LiveBroadcastEngine: ObservableObject {
@Published private(set) var metrics = BroadcastMetrics()
let mixer = MediaMixer()
let connection = RTMPConnection()
let stream: RTMPStream
private let overlayRenderer = OverlayRenderer()
private var config: BroadcastConfig?
private var phase: BroadcastPhase = .idle
private var metricsListener: ((BroadcastMetrics) -> Void)?
private var reconnectAttempts = 0
private var publishTask: Task<Void, Never>?
private var readyStateTask: Task<Void, Never>?
private var pipelineConfigured = false
private var isPortrait = false
private var orientationObserver: NSObjectProtocol?
private weak var previewView: MTHKView?
private var previewAttached = false
private var publishPending = false
private var publishInFlight = false
private var rtmpSession: (any Session)?
private static var rtmpFactoryRegistered = false
init() {
stream = RTMPStream(connection: connection)
private static func ensureRTMPFactoryRegistered() async {
guard !rtmpFactoryRegistered else { return }
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
rtmpFactoryRegistered = true
}
func setMetricsListener(_ block: ((BroadcastMetrics) -> Void)?) {
@@ -53,50 +57,71 @@ final class LiveBroadcastEngine: ObservableObject {
overlayRenderer.update(state: state, isPortrait: isPortrait)
}
/// Anteprima locale: output del mixer (come HaishinKit PublishViewModel), non dello stream RTMP.
func bindPreview(to view: MTHKView) async {
await stream.addOutput(view)
previewView = view
guard pipelineConfigured, !previewAttached else { return }
await mixer.addOutput(view)
previewAttached = true
if publishPending {
await startPendingPublish()
}
}
func preparePreview(config: BroadcastConfig) async throws {
self.config = config
publishPending = false
try await configurePipeline(config)
setPhase(.preview)
}
func startBroadcast(config: BroadcastConfig) async throws {
/// Configura camera/mixer e mette in coda il publish RTMP (eseguito quando l'anteprima è pronta).
func prepareForBroadcast(config: BroadcastConfig) async throws {
self.config = config
publishTask?.cancel()
publishTask = nil
publishPending = true
try await configurePipeline(config)
setPhase(.connecting)
try await Task.sleep(nanoseconds: 350_000_000)
try await publish(config: config)
await startPendingPublish()
}
func startBroadcast(config: BroadcastConfig) async throws {
try await prepareForBroadcast(config: config)
}
func pauseBroadcast() async {
try? await stream.close()
publishPending = false
publishTask?.cancel()
publishTask = nil
await teardownRTMP(keepPreview: true)
setPhase(.paused)
}
func resumeBroadcast(config: BroadcastConfig) async throws {
self.config = config
publishPending = true
if !pipelineConfigured {
try await configurePipeline(config)
}
setPhase(.connecting)
try await publish(config: config)
await startPendingPublish()
}
func stopBroadcast() async {
publishTask?.cancel()
publishTask = nil
publishPending = false
publishInFlight = false
stopOrientationMonitoring()
await teardownRTMP(keepPreview: false)
if let previewView, previewAttached {
await mixer.removeOutput(previewView)
}
previewAttached = false
previewView = nil
overlayRenderer.detach()
try? await stream.close()
try? await connection.close()
try? await mixer.stopRunning()
pipelineConfigured = false
setPhase(.idle)
reconnectAttempts = 0
}
func release() async {
@@ -126,19 +151,22 @@ final class LiveBroadcastEngine: ObservableObject {
if let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
try await mixer.attachVideo(camera, track: 0)
}
try await mixer.addOutput(stream)
try await mixer.startRunning()
overlayRenderer.attach(mixer: mixer, width: config.width, height: config.height, isPortrait: config.portrait)
await applyVideoOrientation()
startOrientationMonitoring()
var streamSettings = await stream.videoSettings
streamSettings.videoSize = .init(width: config.width, height: config.height)
streamSettings.bitRate = config.videoBitrate
streamSettings.maxKeyFrameIntervalDuration = 2
try await stream.setVideoSettings(streamSettings)
var audioSettings = await stream.audioSettings
audioSettings.bitRate = config.audioBitrate
try await stream.setAudioSettings(audioSettings)
try await mixer.startRunning()
overlayRenderer.attach(
mixer: mixer,
width: config.width,
height: config.height,
isPortrait: isPortrait
)
pipelineConfigured = true
if let previewView, !previewAttached {
await mixer.addOutput(previewView)
previewAttached = true
}
}
private func configureScreenSize(width: Int, height: Int) async {
@@ -148,23 +176,175 @@ final class LiveBroadcastEngine: ObservableObject {
}.value
}
private func startPendingPublish() async {
guard publishPending, !publishInFlight else { return }
publishTask?.cancel()
publishTask = Task {
await waitForPreviewThenPublish()
}
}
private func waitForPreviewThenPublish() async {
guard publishPending, let config else { return }
publishInFlight = true
defer { publishInFlight = false }
for _ in 0..<80 {
if Task.isCancelled { return }
if previewAttached { break }
try? await Task.sleep(nanoseconds: 50_000_000)
}
guard previewAttached else {
publishPending = false
setPhase(.error, error: "Anteprima camera non pronta")
return
}
setPhase(.connecting)
try? await Task.sleep(nanoseconds: 350_000_000)
guard publishPending else { return }
do {
try await publish(config: config)
publishPending = false
} catch {
publishPending = false
let message = UserFacingError.message(for: error) ?? "Connessione RTMP fallita"
setPhase(.error, error: message)
}
}
private func publish(config: BroadcastConfig) async throws {
guard let target = RTMPUrlParser.parse(config.rtmpUrl) else {
guard let url = URL(string: config.rtmpUrl) else {
throw APIError.http(400, "URL RTMP non valido")
}
try await connection.connect(target.connectURL)
try await stream.publish(target.streamName)
reconnectAttempts = 0
await Self.ensureRTMPFactoryRegistered()
await applyVideoOrientation()
await teardownRTMP(keepPreview: true)
let session = try await SessionBuilderFactory.shared.make(url)
.setMode(.publish)
.build()
guard let session else {
throw APIError.http(500, "Impossibile creare sessione RTMP")
}
let stream = await session.stream
try await applyCodecSettings(to: stream, config: config)
await stream.setBitRateStrategy(BroadcastBitrateMonitor { [weak self] kbps in
Task { @MainActor in
guard let self else { return }
self.metrics = BroadcastMetrics(
phase: self.phase,
bitrateKbps: kbps,
fps: self.config?.fps ?? self.metrics.fps,
connected: self.phase == .live,
lastError: self.metrics.lastError
)
self.metricsListener?(self.metrics)
}
})
await mixer.addOutput(stream)
rtmpSession = session
observeReadyState(session)
try await session.connect { [weak self] in
Task { @MainActor in
guard let self, self.phase == .live else { return }
self.setPhase(.error, error: "Connessione RTMP interrotta")
}
}
setPhase(.live)
}
private func applyCodecSettings(to stream: any StreamConvertible, config: BroadcastConfig) async throws {
var videoSettings = await stream.videoSettings
videoSettings.videoSize = .init(width: config.width, height: config.height)
videoSettings.bitRate = config.videoBitrate
videoSettings.maxKeyFrameIntervalDuration = 2
videoSettings.profileLevel = kVTProfileLevel_H264_Baseline_3_1 as String
videoSettings.expectedFrameRate = Double(config.fps)
try await stream.setVideoSettings(videoSettings)
var audioSettings = await stream.audioSettings
audioSettings = AudioCodecSettings(
bitRate: config.audioBitrate,
downmix: audioSettings.downmix,
channelMap: audioSettings.channelMap,
sampleRate: 48_000,
format: audioSettings.format
)
try await stream.setAudioSettings(audioSettings)
}
private func observeReadyState(_ session: any Session) {
readyStateTask?.cancel()
readyStateTask = Task {
for await state in await session.readyState {
switch state {
case .open:
setPhase(.live)
case .closed where phase == .live:
setPhase(.error, error: "Connessione RTMP interrotta")
default:
break
}
}
}
}
private func teardownRTMP(keepPreview: Bool) async {
readyStateTask?.cancel()
readyStateTask = nil
if let session = rtmpSession {
let stream = await session.stream
await mixer.removeOutput(stream)
try? await session.close()
}
rtmpSession = nil
if !keepPreview {
publishPending = false
}
}
private func applyVideoOrientation() async {
let orientation = BroadcastVideoOrientation.captureOrientation()
await mixer.setVideoOrientation(orientation)
let portrait = BroadcastVideoOrientation.isPortraitContent
guard portrait != isPortrait else { return }
isPortrait = portrait
overlayRenderer.refreshOrientation(isPortrait: portrait)
}
private func startOrientationMonitoring() {
stopOrientationMonitoring()
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
orientationObserver = NotificationCenter.default.addObserver(
forName: UIDevice.orientationDidChangeNotification,
object: nil,
queue: .main
) { [weak self] _ in
Task { @MainActor in
await self?.applyVideoOrientation()
}
}
}
private func stopOrientationMonitoring() {
if let orientationObserver {
NotificationCenter.default.removeObserver(orientationObserver)
self.orientationObserver = nil
}
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
private func setPhase(_ newPhase: BroadcastPhase, error: String? = nil) {
phase = newPhase
metrics = BroadcastMetrics(
phase: newPhase,
bitrateKbps: metrics.bitrateKbps,
fps: config?.fps ?? metrics.fps,
connected: newPhase == .live || newPhase == .reconnecting,
connected: newPhase == .live,
lastError: error
)
metricsListener?(metrics)

View File

@@ -79,7 +79,12 @@ struct BroadcastScreen: View {
.onChange(of: scoreController.lastActionError) { message in
if let message { snackbarMessage = message }
}
.onChange(of: broadcastCoordinator.metrics.phase) { _ in updateOverlay() }
.onChange(of: broadcastCoordinator.metrics.phase) { phase in
updateOverlay()
if phase == .error, let message = broadcastCoordinator.metrics.lastError {
self.error = message
}
}
.alert("Errore", isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) {
Button("OK") { onFinished() }
} message: {
@@ -287,18 +292,19 @@ struct BroadcastScreen: View {
match = loadedMatch
container.scoreController.bind(sessionId: sessionId, initial: loaded.score)
wireCable()
await preloadLogos(for: loadedMatch)
loading = false
updateOverlay()
try? await Task.sleep(nanoseconds: 100_000_000)
if let url = loaded.rtmpIngestUrl, !url.isEmpty {
let config = broadcastConfig(for: loaded, rtmpUrl: url)
if loaded.isPaused {
try await container.broadcastCoordinator.engine.preparePreview(config: config)
} else {
try await container.broadcastCoordinator.startBroadcast(config: config)
try await container.broadcastCoordinator.engine.prepareForBroadcast(config: config)
}
}
await preloadLogos(for: loadedMatch)
startPolling()
loading = false
updateOverlay()
} catch {
if let message = UserFacingError.message(for: error) {
self.error = message

View File

@@ -58,13 +58,13 @@ struct LoginScreen: View {
_ = try await container.authRepository.login(email: email.trimmingCharacters(in: .whitespaces), password: password)
onLoggedIn()
} catch {
let message = error.localizedDescription
if message.contains("401") {
if let apiError = error as? APIError, case .unauthorized = apiError {
self.error = "Email o password non corretti"
} else if message.localizedCaseInsensitiveContains("timeout") {
} else if let message = UserFacingError.message(for: error),
message.localizedCaseInsensitiveContains("timeout") {
self.error = "Server non raggiungibile. Verifica la connessione."
} else {
self.error = message
self.error = UserFacingError.message(for: error) ?? "Accesso non riuscito"
}
}
loading = false

View File

@@ -1,3 +1,5 @@
import HaishinKit
import RTMPHaishinKit
import SwiftUI
import UIKit
@@ -77,6 +79,9 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
AppOrientation.lockPortrait()
Task {
await SessionBuilderFactory.shared.register(RTMPSessionFactory())
}
return true
}