Core Concepts
Vio brings live engagement to sports streaming. Polls, contests, and shoppable products are configured in the Dashboard and delivered in real-time via WebSocket to the SDK.
Architecture Overview
Dashboard Backend SDK
───────── ─────── ───
Create Campaign → Campaign API → CampaignManager
├── Broadcast ├── /v1/sdk/broadcast ├── discoverCampaigns()
│ └── externalId │ (by contentId) │
├── Polls (scheduled) └── WebSocket /ws/:id → EngagementManager
├── Contests ├── poll event ├── polls
├── Sponsor (logo, color) ├── contest event ├── contests
├── Sponsor Moments ├── sponsor event ├── sponsorSlots
│ └── product/lead/link └── lineup_show → LineupTimelineHandler
└── Lineup (auto at -10min) └── MatchLineupViewKey principle: Everything is configured in the Dashboard. The SDK receives it via API and WebSocket — no hardcoded campaign IDs in your app code.
CampaignManager
The central manager. Use as a singleton via CampaignManager.shared.
import VioCore
// Connect to a broadcast (call when user opens a stream)
await VioSDK.setContent(id: "real-madrid-vs-barcelona-2025-01-24")
// Read state
let campaign = CampaignManager.shared.currentCampaign
let isConnected = CampaignManager.shared.isConnected
// Disconnect (call when user exits the stream)
VioSDK.disconnect()What setContent(id:) does
- Clears components from the previous broadcast
- Calls the backend to find the active campaign for this
broadcastId(matchesexternalIdin Dashboard) - Loads engagement config for the broadcast
- Opens a WebSocket connection
- Backend sends current polls/contests immediately on connect
- New polls/contests arrive in real-time as they trigger during the match
BroadcastContext
The identifier that links your app to a broadcast configured in the Dashboard.
let context = BroadcastContext(
broadcastId: "real-madrid-vs-barcelona-2025-01-24", // Required — matches Dashboard externalId
broadcastName: "Real Madrid vs Barcelona", // Optional — display name
channelId: nil, // Optional
metadata: nil // Optional
)The
broadcastIdyou pass must match theexternalIdfield of the broadcast in the Dashboard exactly.
EngagementManager
Manages the state of polls and contests for the current broadcast. Updated automatically via WebSocket.
import VioEngagementSystem
// Observe polls
@StateObject var engagement = EngagementManager.shared
// In your view
ForEach(engagement.activePollsForCurrentBroadcast) { poll in
PollCard(poll: poll)
}Polls and contests arrive from the WebSocket and are stored here. The VioCastingUI components observe this automatically — you don’t need to manage state manually when using the pre-built overlays.
Sponsor Moments
Sponsor Moments are timed product placements that appear at specific match minutes (e.g., minute 35, 45, 70). They’re configured in the Dashboard under each broadcast — the SDK receives them via WebSocket and renders them automatically.
Slot types: product · lead · poll_cta · contest_cta · link
The public name is Sponsor Moments. In the API and backend they’re
sponsor_slots— both terms refer to the same thing.
Lineup
Match lineups are delivered via the lineup_show WebSocket event. The backend triggers this automatically 10 minutes before kickoff. The MatchLineupView component listens for this event and renders both teams’ formations — no manual data fetching required.
// Just pass the broadcastId — the lineup arrives automatically
MatchLineupView(broadcastId: "real-madrid-vs-mancity-2026-03-11")SponsorAssets
Sponsor identity comes from the backend — no hardcoding needed. Colors, logos, and avatar URLs are delivered via the campaign API.
import VioDesignSystem
// Access sponsor assets (available after discoverCampaigns)
let sponsor = CampaignManager.shared.currentCampaign?.sponsor
let logoUrl = sponsor?.logoUrl
let primaryColor = sponsor?.primaryColor // e.g. "#F5153B" for Viaplay
let avatarUrl = sponsor?.avatarUrlViaCastingUI components use SponsorAssets automatically — sponsor branding is applied without any code changes.
Module Overview
| Module | Key Classes | Use When |
|---|---|---|
VioCore | VioConfiguration, CampaignManager, ConfigurationLoader | Always |
VioEngagementSystem | EngagementManager, CampaignWebSocketManager | Live engagement |
VioEngagementUI | VioEngagementPollOverlay, VioEngagementProductOverlay | Engagement UI |
VioCastingUI | TimelinePollCard, SponsorBadge, ContestCard | Sports casting UI |
VioUI | ProductService, CartManager, VioProductCard | Shoppable products |
VioDesignSystem | VioColors, VioSpacing, VioTypography | Custom UI |
Configuration Flow
@main
struct MyApp: App {
init() {
// 1. Configure with API key — fetches runtime config from backend
VioSDK.configure(apiKey: "your-api-key")
// 2. CampaignManager initializes — waits for setContent(id:)
}
}
// Later, when user opens a stream:
// 3. setContent → discoverCampaigns → WebSocket connects → polls/contests arrive
await VioSDK.setContent(id: "real-madrid-vs-mancity-2026-03-11")Market Availability
The SDK checks market availability using the user’s country code. If the streaming service isn’t available in that market, the SDK disables itself silently.
// After loadConfiguration:
let available = VioConfiguration.shared.isMarketAvailable
let shouldShow = VioConfiguration.shared.shouldUseSDK // isConfigured && isMarketAvailableDesign System
Access design tokens from VioDesignSystem:
import VioDesignSystem
Text("Live")
.foregroundColor(VioColors.primary.toColor())
VStack(spacing: VioSpacing.md) {
VioButton(title: "Add to Cart", style: .primary) { }
}Next Steps
- Components — Full component reference
- API Reference — Models and service APIs
- User Identification — Per-user analytics and attribution
- Zero-Config SDK — Initialization reference