diff --git a/backend/app/controllers/public/regia_controller.rb b/backend/app/controllers/public/regia_controller.rb index 9da7c1c..83c8404 100644 --- a/backend/app/controllers/public/regia_controller.rb +++ b/backend/app/controllers/public/regia_controller.rb @@ -34,6 +34,7 @@ module Public def score cmd = params[:cmd].presence || params[:score_action].presence result = Scoring::ApplyAction.new(session: @session, action: cmd).call + @session.reload render json: status_payload.merge( set_won: result.set_won, match_won: result.match_won, diff --git a/backend/app/models/score_state.rb b/backend/app/models/score_state.rb index 60adff0..51a88e2 100644 --- a/backend/app/models/score_state.rb +++ b/backend/app/models/score_state.rb @@ -55,7 +55,7 @@ class ScoreState < ApplicationRecord end def current_period - (data || {})["period"].to_i.positive? ? (data || {})["period"].to_i : 1 + Scoring::PeriodData.number_from((data || {})["period"]) || 1 end def current_period_label diff --git a/backend/app/services/scoring/engines/period_engine_mixin.rb b/backend/app/services/scoring/engines/period_engine_mixin.rb index 5149b0f..6dc35d9 100644 --- a/backend/app/services/scoring/engines/period_engine_mixin.rb +++ b/backend/app/services/scoring/engines/period_engine_mixin.rb @@ -28,6 +28,7 @@ module Scoring def ensure_period_data(score) data = period_data(score) default_data.each { |k, v| data[k] = v unless data.key?(k) } + PeriodData.normalize_hash!(data) data end @@ -43,7 +44,7 @@ module Scoring end def period_label(data) - period = data["period"].to_i + period = PeriodData.number_from(data["period"]) || 1 return "OT" if data["overtime"] board = @match.effective_board_type @@ -63,15 +64,17 @@ module Scoring rules = period_rules max_periods = rules.periods + current = PeriodData.number_from(data["period"]) || 1 + if data["overtime"] - data["period"] = data["period"].to_i + 1 + data["period"] = current + 1 data["clock_secs"] = rules.overtime_duration_secs - elsif data["period"].to_i >= max_periods + elsif current >= max_periods data["overtime"] = true data["period"] = max_periods + 1 data["clock_secs"] = rules.overtime_duration_secs else - data["period"] = data["period"].to_i + 1 + data["period"] = current + 1 data["clock_secs"] = rules.period_duration_secs end data["clock_running"] = false @@ -109,7 +112,14 @@ module Scoring score = ensure_score(session) data = ensure_period_data(score) %w[period clock_secs clock_running home_score away_score overtime].each do |key| - data[key] = payload[key] if payload.key?(key) + next unless payload.key?(key) + + if key == "period" + period = PeriodData.number_from(payload[key]) + data["period"] = period if period&.positive? + else + data[key] = payload[key] + end end save_period_score!(score, data) score diff --git a/backend/app/services/scoring/period_data.rb b/backend/app/services/scoring/period_data.rb new file mode 100644 index 0000000..9753168 --- /dev/null +++ b/backend/app/services/scoring/period_data.rb @@ -0,0 +1,32 @@ +module Scoring + # Normalizza `score_states.data["period"]` (intero o etichetta "Q2" / "2° tempo"). + module PeriodData + module_function + + def number_from(raw) + case raw + when Integer, Float + value = raw.to_i + value.positive? ? value : nil + when String + text = raw.strip + return nil if text.empty? + + if (match = text.match(/\AQ(\d+)\z/i)) + match[1].to_i + elsif (match = text.match(/\A(\d+)\s*°/)) + match[1].to_i + elsif (value = text.to_i).positive? + value + end + end + end + + def normalize_hash!(data) + number = number_from(data["period"]) + data["period"] = number if number&.positive? + data["period"] = 1 unless data["period"].to_i.positive? + data + end + end +end diff --git a/backend/app/views/public/regia/_basket.html.erb b/backend/app/views/public/regia/_basket.html.erb index eeb6f72..7d1314d 100644 --- a/backend/app/views/public/regia/_basket.html.erb +++ b/backend/app/views/public/regia/_basket.html.erb @@ -2,7 +2,6 @@
<%= live_score_period_label(@match, @score) %>
<%= live_score_points_label(@match, @score) %>
-Cronometro: <%= format_clock_secs(data["clock_secs"]) %>
<%= live_score_period_label(@match, @score) %>
<%= live_score_points_label(@match, @score) %>
-Cronometro: <%= format_clock_secs(data["clock_secs"]) %>
Regia · <%= regia_header_subtitle(@match, @score) %>
+Regia · <%= regia_header_subtitle(@match, @score) %>
<%= @stream_closed ? "Terminata" : (@session.paused? ? "In pausa" : (@on_air ? "In onda" : "In attesa")) %> @@ -93,4 +93,4 @@ - + diff --git a/backend/public/regia.js b/backend/public/regia.js index e591180..8408c92 100644 --- a/backend/public/regia.js +++ b/backend/public/regia.js @@ -35,7 +35,8 @@ modalCancel: document.getElementById("modal-cancel"), preview: document.getElementById("regia-preview"), previewPlaceholder: document.getElementById("regia-preview-placeholder"), - btnPause: document.getElementById("btn-pause") + btnPause: document.getElementById("btn-pause"), + subtitle: document.getElementById("regia-subtitle") }; let pendingAction = null; @@ -99,6 +100,31 @@ return `${m}:${String(s).padStart(2, "0")}`; } + function resolvePeriodLabel(score, board) { + const raw = score.period; + if (typeof raw === "string" && raw.trim()) return raw.trim(); + if (typeof raw === "number" && raw > 0) { + return board === "basket" ? `Q${raw}` : `${raw}° tempo`; + } + const nested = score.data || {}; + const dataPeriod = nested.period ?? nested["period"]; + const periodNum = parseInt(dataPeriod, 10); + if (periodNum > 0) { + if (nested.overtime || nested["overtime"]) { + return board === "basket" ? `OT${Math.max(periodNum - 4, 1)}` : "Suppl."; + } + return board === "basket" ? `Q${periodNum}` : `${periodNum}° tempo`; + } + return "—"; + } + + function updateRegiaSubtitle(periodLabel, board) { + if (!els.subtitle) return; + if (board === "basket" || board === "timed") { + els.subtitle.textContent = `Regia · ${periodLabel}`; + } + } + function applyScorePayload(data) { const s = data.score; if (!s) return; @@ -109,8 +135,7 @@ const homeSets = s.home_sets ?? s.homeSets; const awaySets = s.away_sets ?? s.awaySets; const currentSet = s.current_set ?? s.currentSet; - const period = s.period; - const clockSecs = s.clock_secs ?? s.clockSecs ?? s.data?.clock_secs; + const periodLabel = resolvePeriodLabel(s, board); const awayEl = document.getElementById("away-points"); const centerHint = document.getElementById("center-hint"); const clockLine = document.getElementById("clock-line"); @@ -119,11 +144,12 @@ if (awayEl) awayEl.textContent = awayPts; if (board === "basket" || board === "timed") { - if (els.setsLine) els.setsLine.textContent = period || "—"; + if (els.setsLine) els.setsLine.textContent = periodLabel; if (els.scoreLine) els.scoreLine.textContent = `${cfg.homeName} ${homePts} - ${awayPts} ${cfg.awayName}`; - if (centerHint) centerHint.textContent = formatClock(clockSecs); - if (clockLine) clockLine.textContent = `Cronometro: ${formatClock(clockSecs)}`; + if (centerHint) centerHint.textContent = periodLabel; + updateRegiaSubtitle(periodLabel, board); } else if (board === "timer") { + const clockSecs = s.clock_secs ?? s.clockSecs ?? s.data?.clock_secs; if (els.setsLine) els.setsLine.textContent = "Cronometro"; if (clockLine) clockLine.textContent = formatClock(clockSecs, true); } else if (board === "generic") { @@ -184,11 +210,18 @@ async function handleAction(action) { const prevHome = els.homePoints?.textContent; const prevAway = document.getElementById("away-points")?.textContent; + const trigger = root.querySelector(`[data-action="${action}"]`); + if (trigger) trigger.disabled = true; try { const data = await postScore(action); applyScorePayload(data); setBadge(data); + if (action === "advance_period") { + const periodLabel = resolvePeriodLabel(data.score || {}, cfg.board); + toast(periodLabel === "—" ? "Periodo aggiornato" : `Ora in ${periodLabel}`); + } + if (cfg.board === "volley" || cfg.board === "racket") { if (data.set_won && action !== "close_set") { const winner = data.winner === "home" ? cfg.homeName : cfg.awayName; @@ -204,6 +237,8 @@ const awayEl = document.getElementById("away-points"); if (prevAway != null && awayEl) awayEl.textContent = prevAway; toast(e.message || "Errore"); + } finally { + if (trigger) trigger.disabled = false; } } diff --git a/backend/spec/requests/public/regia_spec.rb b/backend/spec/requests/public/regia_spec.rb index f01e0da..5f85e17 100644 --- a/backend/spec/requests/public/regia_spec.rb +++ b/backend/spec/requests/public/regia_spec.rb @@ -62,6 +62,72 @@ RSpec.describe "Public regia", type: :request do expect(response.parsed_body.dig("score", "board_type")).to eq("basket") end + it "avanza il quarto basket via API" do + basket_team = club.teams.create!(name: "U13 Basket", sport_key: "basket") + basket_match = basket_team.matches.create!(opponent_name: "Avversario", sport: "basket") + basket_session = StreamSession.create!(match: basket_match, user: user, platform: "matchlivetv", status: "live") + basket_session.create_score_state!( + board_type: "basket", + home_sets: 0, + away_sets: 0, + home_points: 0, + away_points: 0, + current_set: 1, + data: { + "period" => 1, + "clock_secs" => 600, + "clock_running" => false, + "home_score" => 0, + "away_score" => 4, + "overtime" => false + } + ) + token = Sessions::RegiaAccess.new(basket_session).issue_token! + + patch public_regia_score_path(token), params: { cmd: "advance_period" }, as: :json + expect(response).to have_http_status(:ok) + body = response.parsed_body + expect(body.dig("score", "period")).to eq("Q2") + expect(body.dig("score", "data", "period")).to eq(2) + expect(basket_session.score_state.reload.data["period"]).to eq(2) + end + + it "avanza il quarto basket anche con periodo Q1 corrotto in data" do + basket_team = club.teams.create!(name: "U13 Basket", sport_key: "basket") + basket_match = basket_team.matches.create!(opponent_name: "Avversario", sport: "basket") + basket_session = StreamSession.create!(match: basket_match, user: user, platform: "matchlivetv", status: "live") + basket_session.create_score_state!( + board_type: "basket", + data: { + "period" => "Q1", + "home_score" => 0, + "away_score" => 4, + "clock_secs" => 600, + "clock_running" => false, + "overtime" => false + } + ) + token = Sessions::RegiaAccess.new(basket_session).issue_token! + + patch public_regia_score_path(token), params: { cmd: "advance_period" }, as: :json + expect(response).to have_http_status(:ok) + expect(response.parsed_body.dig("score", "period")).to eq("Q2") + end + + it "non mostra controlli cronometro sulla regia basket" do + basket_team = club.teams.create!(name: "U13 Basket", sport_key: "basket") + basket_match = basket_team.matches.create!(opponent_name: "Avversario", sport: "basket") + basket_session = StreamSession.create!(match: basket_match, user: user, platform: "matchlivetv", status: "live") + basket_session.create_score_state!(board_type: "basket", data: { "period" => 1, "home_score" => 0, "away_score" => 0 }) + token = Sessions::RegiaAccess.new(basket_session).issue_token! + + get public_regia_path(token) + expect(response).to have_http_status(:ok) + expect(response.body).to include("Prossimo quarto") + expect(response.body).not_to include("cronometro") + expect(response.body).not_to include("Cronometro:") + end + it "mette in pausa e riprende la diretta" do mtx = instance_double( Mediamtx::Client, diff --git a/backend/spec/services/scoring/period_data_spec.rb b/backend/spec/services/scoring/period_data_spec.rb new file mode 100644 index 0000000..4898d2b --- /dev/null +++ b/backend/spec/services/scoring/period_data_spec.rb @@ -0,0 +1,66 @@ +require "rails_helper" + +RSpec.describe Scoring::PeriodData do + describe ".number_from" do + it "legge interi e etichette basket/timed" do + expect(described_class.number_from(2)).to eq(2) + expect(described_class.number_from("Q3")).to eq(3) + expect(described_class.number_from("2° tempo")).to eq(2) + expect(described_class.number_from("Q1")).to eq(1) + expect(described_class.number_from("")).to be_nil + end + end +end + +RSpec.describe "Basket advance_period con periodo corrotto" do + let!(:user) { User.create!(email: "period@test.it", name: "U", password: "password123", role: "coach") } + let!(:club) { Club.create!(name: "C", sport: "basket", primary_color: "#e53935", secondary_color: "#ffffff") } + let!(:team) { club.teams.create!(name: "U13", sport_key: "basket") } + let!(:match) { team.matches.create!(opponent_name: "Opp", sport: "basket") } + let!(:session) { StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "live") } + + it "avanza il quarto anche se data.period è la stringa Q1 (sync app mobile)" do + session.create_score_state!( + board_type: "basket", + home_points: 0, + away_points: 4, + data: { + "period" => "Q1", + "home_score" => 0, + "away_score" => 4, + "clock_secs" => 600, + "clock_running" => false, + "overtime" => false + } + ) + + Scoring::ApplyAction.new(session: session, action: "advance_period").call + score = session.score_state.reload + + expect(score.data["period"]).to eq(2) + expect(score.current_period_label).to eq("Q2") + expect(score.as_cable_payload[:period]).to eq("Q2") + end + + it "normalizza period in sync Action Cable" do + session.create_score_state!( + board_type: "basket", + data: { "period" => 1, "home_score" => 0, "away_score" => 0 } + ) + + Scoring::SyncState.new( + session: session, + payload: { + "type" => "score_update", + "board_type" => "basket", + "period" => "Q1", + "home_score" => 2, + "away_score" => 1 + } + ).call + + score = session.score_state.reload + expect(score.data["period"]).to eq(1) + expect(score.data["home_score"]).to eq(2) + end +end diff --git a/native/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/domain/ScoreState.kt b/native/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/domain/ScoreState.kt index 2f7a16b..d2bc458 100644 --- a/native/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/domain/ScoreState.kt +++ b/native/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/domain/ScoreState.kt @@ -38,7 +38,7 @@ data class ScoreState( "timeout_away" to timeoutAway, ) if (boardType in PERIOD_BOARDS) { - payload["period"] = periodLabel ?: period.toString() + payload["period"] = period payload["clock_secs"] = clockSecs payload["clock_running"] = clockRunning payload["home_score"] = homePoints diff --git a/native/ios/MatchLiveTv.xcodeproj/project.pbxproj b/native/ios/MatchLiveTv.xcodeproj/project.pbxproj index f61cedf..4a55ebe 100644 --- a/native/ios/MatchLiveTv.xcodeproj/project.pbxproj +++ b/native/ios/MatchLiveTv.xcodeproj/project.pbxproj @@ -7,183 +7,183 @@ objects = { /* Begin PBXBuildFile section */ - 00BDF215CB7942A799F06774 /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6D45B70859402BA3ABD014 /* TeamColorPicker.swift */; }; - 045086A670F24B1192E0DF8F /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C3663CA3AD648B48EA4405D /* SessionRepository.swift */; }; - 08F123F9692D48EA94EBD94D /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0DC8A3443404B0CAD323B11 /* KeepScreenOn.swift */; }; - 0CE091822CAA4D88B1BE3AF4 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAEE3C8BBB684FEBA230480A /* LoginScreen.swift */; }; - 0E03BE40DAF14E98A35051CE /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8EB774A34C4E259AFB1C88 /* ScoreRepository.swift */; }; - 138294291C614F218D71B1CD /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3A3F3250284150B0A0BCD4 /* ModalRoutes.swift */; }; - 15EBD3E3D95D4AE5BABB6776 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA61FF84F91473DA84438A8 /* ScoreboardElement.swift */; }; - 191F31088FCE4A82932B6FBB /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D1C27A80A842A1B531051C /* LiveScoreActions.swift */; }; - 22A2A4F3BBD845C5B4E1DD6E /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5C7905A24E4AE1A0600511 /* DeviceTelemetry.swift */; }; - 2498A1488A034D058E6997B0 /* ScoreActionDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BA0FAD47DC40D48F063FC8 /* ScoreActionDecodeTests.swift */; }; - 284F82D0FB65448BAB38C6BE /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D2302B51D94A8984EC6A56 /* TokenStore.swift */; }; - 28A607B3D84448308B130ECF /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DBA84B5B0CA459E89779309 /* WatermarkElement.swift */; }; - 2D3FAEEE0372415381467FC7 /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DF95A27A584535A1511035 /* ApiDtos.swift */; }; - 3027E88493BA43B09B150BA1 /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9316C1429F9448ABC7BA7B6 /* MatchSessionLauncher.swift */; }; - 30F21F19C7234BBF8B2B77D4 /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FFA851A3BC44A784DF353A /* BroadcastPermissions.swift */; }; - 35FE18027B054D5BBC20DC9C /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049297E3B0D44FF8840B8E76 /* MatchScoringRulesTests.swift */; }; - 39E01672805E4B8A88DA0FA9 /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D36C4F09E5D04392B0B82EF8 /* ActionCableClient.swift */; }; - 3A08990CB238458CA25C07C9 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D603A791C926456D9B565F78 /* ShareSheet.swift */; }; - 3AA28F966201426380DEE174 /* BroadcastOrientationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43DEDBA98F24BE996528018 /* BroadcastOrientationPolicyTests.swift */; }; - 4598E75CCCF5492CB2F0B006 /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2349A6BFDD4708B821C659 /* MatchColors.swift */; }; - 464C286CAE724604AF9CC1FD /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1144C664590842258CBE9998 /* MatchSecondaryButton.swift */; }; - 48719B5718B2419E96AE6AF2 /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8AA247685DD45FD980465B0 /* SessionCableService.swift */; }; - 4A67DD768F074E7885DFDF43 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E61E730231499C9F119309 /* WizardShellScreen.swift */; }; - 4A6973ACDCA743B29ACC1E2E /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C397B3162F38497C9AD0C9CF /* OverlayRenderer.swift */; }; - 4BD05489BDAB4E309311B1C7 /* BroadcastVideoOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA94A102E6C4E1BAF934D53 /* BroadcastVideoOrientation.swift */; }; - 4C2639382FE6A0440015AAF3 /* HaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = DFA0D0CEB1F1428D9E3FB3DB /* HaishinKit */; }; - 4C2639392FE6A0440015AAF3 /* RTMPHaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = AA756D8C68BC4B3D8EC96D5E /* RTMPHaishinKit */; }; - 4CD8A3EB76C14F878BF1026C /* LiveScoreDialogHostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42DAF0103FD4213BE778C7C /* LiveScoreDialogHostTests.swift */; }; - 51B03D8318114D3AB94A41CA /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D65108DA4C4A5BB95C0E51 /* SplashScreen.swift */; }; - 52DCD38F408B4BAE8A0A673D /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A62D39BF1BD343E2B314A150 /* AppContainer.swift */; }; - 53A492E3510D4C8A9A5C94B7 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C71579DA2945D5B0FA780C /* ScoreController.swift */; }; - 59BB3212FCBA4F68B23337F3 /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD491B8176A0430A9C3AB7B9 /* LiveBroadcastEngine.swift */; }; - 5A8E598237CF41BFB62B83D8 /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF24FBB15BA4493EAEA78436 /* WizardSessionHolder.swift */; }; - 5D4E00AC5D4E44A09E3743DE /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3458504736B4137A749812C /* MatchLiveTvApp.swift */; }; - 5E33700F7C694E9D8EC997D7 /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31852C7972040328E39C991 /* ScreenOrientation.swift */; }; - 644B22C2C7E54983A6BE393D /* LiveBroadcastCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB11018606164DAFBDC4E7A1 /* LiveBroadcastCoordinatorTests.swift */; }; - 66539A1D77EA4B368F5BDE2E /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B469A9557164A2682CEA145 /* StepMatchScreen.swift */; }; - 6C52A9292D694A728A372551 /* ScoreControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C58DA984475413DB55C3E1B /* ScoreControllerTests.swift */; }; - 6CA1DE649AEE455C994D0E94 /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0487DB6D2DEB4508B5EF5FCC /* MediaUrl.swift */; }; - 6CC11041AFC64969BC9195D0 /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4974F7EF092B4FC697A30E00 /* MatchScoringRules.swift */; }; - 6DAD6078CC8D46FCB1AE0A75 /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE69B3EE8D0E4D8EAA8B853B /* MatchLiveWordmark.swift */; }; - 6E09F9E1FBB3459B94343BDD /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224057D043A541C287071B37 /* OverlayLogoCache.swift */; }; - 7862CEBEB345430F908DE135 /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE44F992D17E408D8713CC95 /* OverlayState.swift */; }; - 7B4182E19F93484B8B0D07DF /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3490DEC22A543F890FEA661 /* WizardComponents.swift */; }; - 7DCAA615F27C4853B8A0F889 /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8799BE797C4B4A409E32E753 /* SponsorElement.swift */; }; - 82ADD6499C394031B87D7A96 /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691EB1B5F9EA4DFB902E0D37 /* ApiInstant.swift */; }; - 8BAE2CEB56064BA3A3A16B53 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = D17AB6B00BBC4568A6D0457E /* Models.swift */; }; - 91C697AC57284A26A9893DFD /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8DDF8DE68C4281B9062AD0 /* OverlayMappings.swift */; }; - 91F1D3111F2C4BDFAB7435BF /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8FE87562E85488A946F8C86 /* MatchScreenScaffold.swift */; }; - 921A1A2729B244C08A9BD3FD /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0E8585555C42A6A220FF29 /* BroadcastControlsOverlay.swift */; }; - 96E2427E81E744289D1C9B9B /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC65D1EBC15F4ECB921A94F5 /* BroadcastModels.swift */; }; - 991371D458954C06A798AC0E /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E0CCB4B0554F96B04BDB07 /* LiveBroadcastCoordinator.swift */; }; - 994148BB9CCE4E6F800CD89A /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4770AC3C14D451F89B35533 /* ColorHex.swift */; }; - 9D241B051BD54105A9D764DD /* BroadcastOrientationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD03F804A944D6D84464875 /* BroadcastOrientationPolicy.swift */; }; - 9E770D39394E41838877BC62 /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0B5292C4B7344E98043FA55 /* AuthRepository.swift */; }; - AEE1728EFF3F491A81B1156B /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1123FED73ED4983BFBE9BB6 /* LivePreviewView.swift */; }; - B3DD2F8E837649B2B007CEAF /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4570572C5D4745A990B291D6 /* MatchHubFilter.swift */; }; - B7D592901EC34D4B97DCE549 /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D13551050B4A51A21B0A67 /* StepTransmissionScreen.swift */; }; - C5EEE95F92D44046994659FC /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94110FC43754C50AC862D5C /* BroadcastScreen.swift */; }; - C6B059D6435243208A8FC338 /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40924F02DDFA4CCDA9488D64 /* MatchesScreen.swift */; }; - CAABC65179F6467996D85730 /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5ECE7DA4A94297899495FF /* UserFacingError.swift */; }; - D015D7ACCFE54071B1831C74 /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36263581FBC9465FB59A54A3 /* MatchPrimaryButton.swift */; }; - D0BAD147978244529A46C040 /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9968E881924270973373DD /* CompactScoreboardElement.swift */; }; - D3C28481960E4E14A0909DCB /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290C51105D0B4ABE997FA155 /* Routes.swift */; }; - D501A7EE3BAD467A9A63E63C /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B41DFCE8AF4993A93F756A /* AppNavHost.swift */; }; - D77980669AE84165AAF94AF2 /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76B77E9A6C51421D9AEDFF51 /* MatchRepository.swift */; }; - D916D4368A9A4298BDB3430F /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07588FE10B4143D8971F8A1F /* AppConfig.swift */; }; - EA533D28A4EF4171B410B56E /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134FD1D616EF4F82A0175E28 /* ScoreState.swift */; }; - EB8D708E665A46B398EB0C4F /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BCA039B9842405DBCB38BA5 /* MatchLiveAPI.swift */; }; - EBB063DFDBE443F3BC12A67A /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0FE41B109A41519495E952 /* ApiInstantTests.swift */; }; - EE689B65571A474CA19A792C /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C561AB7965264974822DBD60 /* OverlayCanvasRenderer.swift */; }; - EF83C38F78144E0996C33807 /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0D156DFC16945A3A8467EF0 /* StepNetworkTestScreen.swift */; }; - F13AA7F009F1421FB996956B /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC1BB5EDAEE43359749B95A /* TeamBrandingEditor.swift */; }; - F13DF021E1084F6C844CB7AB /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7FA4E0FD3142D9957B764F /* MatchStatusBadge.swift */; }; - FE21BE8932864B5D96D1BA41 /* MatchLiveTv/Resources/Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FE0CBD099ED14E62B752AB7F /* MatchLiveTv/Resources/Assets.xcassets */; }; + 05E2509B1F96460F9A8FBDEE /* MatchScoringRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51257C93F3A4464F8AA4B5B5 /* MatchScoringRulesTests.swift */; }; + 0811090F35B647A9AAE98018 /* ApiInstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = F52E5A9320FD4A9C82C64092 /* ApiInstant.swift */; }; + 0ABF8F2EAEF84F3CB3510FCA /* MatchHubFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C298D589A644368A5B3DB79 /* MatchHubFilter.swift */; }; + 0AEA80531D74475A9279AAF8 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C77C273929F4A4A9079A55E /* LoginScreen.swift */; }; + 0B2C36335D034737BD753D22 /* BroadcastControlsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9652ABD17F34BF7B781F9CB /* BroadcastControlsOverlay.swift */; }; + 1191C9F3433D4C79BB1D55CA /* DeviceTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD66A152068843A89E25EA2A /* DeviceTelemetry.swift */; }; + 16D77B1BA1694BC9AAEACACA /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7B37C6177BA4FC7AD4DE2C9 /* ScoreState.swift */; }; + 17F54C8204F346CCA9EBFF89 /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6CC04AA64F244128D7069F7 /* TokenStore.swift */; }; + 1B65AFB2A2A54BD69FE9D4D3 /* MatchLiveAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7485A29DA1E4F7EB3D4116D /* MatchLiveAPI.swift */; }; + 1DF38DB813DC491C94C206CD /* SponsorElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF4D8B887F6467699C4E184 /* SponsorElement.swift */; }; + 1F26CA550156447B89B3EC01 /* MatchLiveWordmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D5396F45B5E40B1B1787C6E /* MatchLiveWordmark.swift */; }; + 2094E19DDAD0464B9CED0495 /* ScoreActionDecodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9493EA978E6460FACFB7831 /* ScoreActionDecodeTests.swift */; }; + 228CC71E4BBA4CF0A7712FDD /* ApiInstantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EFBCE62B1AD4A318A676B29 /* ApiInstantTests.swift */; }; + 252646FBD7F94CCEB49EE534 /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C62B9683F44AA2A14D424A /* ScoreController.swift */; }; + 255C09F3A1AB4FCC80DC403D /* KeepScreenOn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E3FD0A68DC4FB6BB6F5DC3 /* KeepScreenOn.swift */; }; + 2F05B27C846F4284A533573D /* AuthRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FF6D203D77042EBAF2DB51F /* AuthRepository.swift */; }; + 2F9195CCAE9949C087CECAA2 /* LiveScoreActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7122CAE2FBF04D59B28E3393 /* LiveScoreActions.swift */; }; + 33848EA261D540879BABC0EC /* LiveBroadcastCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FA1B77E81F48C5B4E55CD9 /* LiveBroadcastCoordinator.swift */; }; + 344B17113ADF4C548146080C /* MatchLiveTv/Resources/Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F01AFBFEF6044C68CB26E01 /* MatchLiveTv/Resources/Assets.xcassets */; }; + 34C4FE9AC892423FA42B0FA1 /* SessionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87978C769F4E4D468052CC33 /* SessionRepository.swift */; }; + 3D6496F4394A4A8B8280CCD7 /* MatchStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351CF0707AD942A58C630254 /* MatchStatusBadge.swift */; }; + 412D04EC8F8344F3941D7774 /* ScoreRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB392D77E64247B3B093F616 /* ScoreRepository.swift */; }; + 44412124BE71497F89A100A7 /* OverlayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A14E10B5AA8D45859AC6A1A4 /* OverlayState.swift */; }; + 4661478CC7C343379C100A7E /* MatchRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00E4AADECC140CDAD8C2AE6 /* MatchRepository.swift */; }; + 4B6FFF5D05164AE9AFB2833C /* StepMatchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FCAE846D294A19A7981CA2 /* StepMatchScreen.swift */; }; + 4BF1F3719177402988F3E680 /* MatchLiveTvApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA1FADDBD6A478FBA4D4047 /* MatchLiveTvApp.swift */; }; + 4C2639442FE6D7CF0015AAF3 /* HaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 363FFDAD444D4381B6D23159 /* HaishinKit */; }; + 4C2639452FE6D7CF0015AAF3 /* RTMPHaishinKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3CEE882EC45E49D49BF6B568 /* RTMPHaishinKit */; }; + 4E3828F9935743CCB4B15F01 /* WizardComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 727F818455854A5C95A36750 /* WizardComponents.swift */; }; + 57499374CE314E5C9B19E857 /* OverlayLogoCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A399280B3F39439986A8B8CA /* OverlayLogoCache.swift */; }; + 5A085BBBD09B49A98A261FD1 /* StepNetworkTestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1575FE5CDD584E119971A738 /* StepNetworkTestScreen.swift */; }; + 5BD7A70803AE4BFCBED02E69 /* WizardSessionHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C680C09D954141B3992BAE /* WizardSessionHolder.swift */; }; + 62147CCDD37243B9AB328357 /* ColorHex.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2944504F54A4FFBBC27890E /* ColorHex.swift */; }; + 62F8E9A68AF84E04B5B25C0E /* LiveBroadcastEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03E8C38FF8F45E3B60876C4 /* LiveBroadcastEngine.swift */; }; + 6312973A866D4191BA2E2E5E /* BroadcastPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B905649882624CD4BDFBAA1E /* BroadcastPermissions.swift */; }; + 64D51E5990C34E64BBDD6153 /* MediaUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F5C50636970455FBD3C4C90 /* MediaUrl.swift */; }; + 6F38CD1E29CE4EDF92D2FCBD /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218CB473E8054075A604031B /* AppConfig.swift */; }; + 70385A94466D4A2EA11DDB3B /* LivePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BE69BF72BE4435BEDA9133 /* LivePreviewView.swift */; }; + 71A4EBF7C11441ED8202C4A5 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EC9ED467E84061B684745A /* SplashScreen.swift */; }; + 72458A3A5D6D40D8B2B4362A /* ActionCableClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E73FCF20F1D4F148B9CD4B3 /* ActionCableClient.swift */; }; + 72B22BECDAD64FE88CD38A25 /* BroadcastOrientationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED568E49E4044838349A540 /* BroadcastOrientationPolicy.swift */; }; + 7360468C628E48AF950EF5CB /* StepTransmissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64DFCC6BD2024FDBAB16BB74 /* StepTransmissionScreen.swift */; }; + 7FAB1EF0F8D3459CB1A05CD5 /* OverlayCanvasRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2481408D6640FB9CC2CB2F /* OverlayCanvasRenderer.swift */; }; + 844259AFBAF448C6ADBDE30C /* ApiDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AD2477B899435F8D082A92 /* ApiDtos.swift */; }; + 85680F15BAC247B1AA97DC4D /* MatchPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB0CC339DE745259373D142 /* MatchPrimaryButton.swift */; }; + 871F7D73227B4E699B65FE3D /* TeamBrandingEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E32781A593654542B0650E57 /* TeamBrandingEditor.swift */; }; + 8B26B443AD5C4FF388D448F5 /* LiveBroadcastCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D916A7D894E119711065F /* LiveBroadcastCoordinatorTests.swift */; }; + 9187B987AC034AAD887F6393 /* BroadcastOrientationPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D291E7B6E7644EC49B08B1E3 /* BroadcastOrientationPolicyTests.swift */; }; + 942E7C8159B2490A9058D20A /* MatchScreenScaffold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324AD8EC837D48F5B6FFCEAA /* MatchScreenScaffold.swift */; }; + 953EFFA2B63946A0818C82A1 /* CompactScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994567EE1DE14A20B92F4E96 /* CompactScoreboardElement.swift */; }; + A5767FD88F8D4123A68224CD /* BroadcastScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72E46A99EFD841C5A1647DB8 /* BroadcastScreen.swift */; }; + ABA512B1D2C7407E892623DE /* WatermarkElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16A9445555754FB7BDAADD3A /* WatermarkElement.swift */; }; + AD41DBD5504E4D3CB34B81B1 /* OverlayMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC64A2E1500E4ADB9ACE1A86 /* OverlayMappings.swift */; }; + AD9A1CB58F8C44EC92C5B4AE /* MatchScoringRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = B820270757EE42759E42FEEE /* MatchScoringRules.swift */; }; + ADA537BB60354316AB3D343D /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500277E50C1A4646920A8543 /* Models.swift */; }; + B6CA255BE2A840A7B21CDCCB /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 638CBB9E0E874F7F95CBE204 /* ShareSheet.swift */; }; + B9DFB757CD3942AEBC1953F0 /* WizardShellScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 828F33DF188D43AFB37FB0FE /* WizardShellScreen.swift */; }; + BCA11E5347BE4070B1883A58 /* AppNavHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90C96BCB45B4C70A4EFEDDA /* AppNavHost.swift */; }; + BDAA05C294674E0A837D27AB /* MatchSecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8363E5303ADB4FD78519684F /* MatchSecondaryButton.swift */; }; + BDF21E4064AC48D2BF2D3182 /* MatchSessionLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99BA29C831B4470DB25B2180 /* MatchSessionLauncher.swift */; }; + C0E26D8B51484304B9234F89 /* BroadcastModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E9DB4FB233435987DE957D /* BroadcastModels.swift */; }; + C2479C699DF24364951AFFEC /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB101F16AF1494E8A143D0A /* Routes.swift */; }; + C6B8E801C3E449ABBFA44F33 /* MatchColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33DE04949BC459E88BE9366 /* MatchColors.swift */; }; + D44A9E22D1C241EA83B7A897 /* BroadcastVideoOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0997030621F749A3B9CDF588 /* BroadcastVideoOrientation.swift */; }; + D4EED1F5722B435695F1C09D /* ScreenOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E331937B64477698ACF9B4 /* ScreenOrientation.swift */; }; + D6ED4D033BA74DFFA968FC50 /* OverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8079EC41BD47469013C0B2 /* OverlayRenderer.swift */; }; + D74402BB72E04D8A816BC980 /* SessionCableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153C1BAFFFD94507B3F4960D /* SessionCableService.swift */; }; + DAA27DA77F904BA9B59684B1 /* ScoreboardElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0128206567441BB807BB13 /* ScoreboardElement.swift */; }; + DF8D47F8D9604F60B03E87F2 /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FF647CAD4FA457A8F37C5F0 /* AppContainer.swift */; }; + E2D089A57F9F49D887C0D233 /* ModalRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9818CDD6BBDF4E68B2F56E32 /* ModalRoutes.swift */; }; + E2E74A83D86544BA8FB85AAB /* TeamColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6338B84D52DA4FBA93DAE49A /* TeamColorPicker.swift */; }; + E8D2C2E91DBE41BB9A1B59AF /* UserFacingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E556E7CE8E4407B926500E /* UserFacingError.swift */; }; + F3907C6CC01447B786FF633F /* MatchesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8258C8C208E4AE2B37636E8 /* MatchesScreen.swift */; }; + FB09138978B3408ABBE91CD9 /* ScoreControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7814A010936047EBB4193446 /* ScoreControllerTests.swift */; }; + FEE2AE0621F94EA393524C47 /* LiveScoreDialogHostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740D32B076064CCDBE6A688F /* LiveScoreDialogHostTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - ACB2FE70CE4841CCB7A0ACC7 /* PBXContainerItemProxy */ = { + 90C62964E51147DCABB6E3C2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 76FC637A5DFB4F018A0D1441 /* Project object */; + containerPortal = DBE4153B1FC34F91BC390462 /* Project object */; proxyType = 1; - remoteGlobalIDString = AE6E9D532CE6492D8B82687F; + remoteGlobalIDString = 2F039DD838234B1F9B1B2FF5; remoteInfo = MatchLiveTv; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 0487DB6D2DEB4508B5EF5FCC /* MediaUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaUrl.swift; path = MatchLiveTv/Core/MediaUrl.swift; sourceTree = "