This document outlines a comprehensive solution for building a cricket streaming MAUI app with subscription plans, geo-restrictions, and preview functionality. Let's break this down into manageable steps.
System Architecture Overview
Frontend: .NET MAUI App Backend: ASP.NET Core Web API Database: SQL Server (or PostgreSQL) Streaming: HLS/DASH via Wowza/FFmpeg Auth: JWT Tokens Geo-Restriction: IP Geolocation Payment: Stripe/Razorpay integration
Step 1: Setup Development Environment
Prerequisites
Visual Studio 2022 (with .NET MAUI workload)
.NET 7/8 SDK
SQL Server (or PostgreSQL)
FFmpeg (for streaming processing)
Step 2: Database Design
-- Users table CREATE TABLE Users ( Id INT PRIMARY KEY IDENTITY, Username NVARCHAR(50) UNIQUE NOT NULL, Email NVARCHAR(100) UNIQUE NOT NULL, PasswordHash NVARCHAR(255) NOT NULL, Salt NVARCHAR(100) NOT NULL, CreatedAt DATETIME DEFAULT GETDATE(), LastLogin DATETIME, DeviceId NVARCHAR(255), IPAddress NVARCHAR(50) ); -- Subscription Plans CREATE TABLE SubscriptionPlans ( Id INT PRIMARY KEY IDENTITY, Name NVARCHAR(50) NOT NULL, Description NVARCHAR(255), Price DECIMAL(10,2) NOT NULL, VideoQuality NVARCHAR(20) NOT NULL, -- 480p, 720p, 1080p IsActive BIT DEFAULT 1 ); -- User Subscriptions CREATE TABLE UserSubscriptions ( Id INT PRIMARY KEY IDENTITY, UserId INT FOREIGN KEY REFERENCES Users(Id), PlanId INT FOREIGN KEY REFERENCES SubscriptionPlans(Id), StartDate DATETIME NOT NULL, EndDate DATETIME NOT NULL, PaymentTransactionId NVARCHAR(255), IsActive BIT DEFAULT 1 ); -- Preview Access CREATE TABLE PreviewAccess ( Id INT PRIMARY KEY IDENTITY, DeviceId NVARCHAR(255) NOT NULL, IPAddress NVARCHAR(50) NOT NULL, LastAccess DATETIME NOT NULL, AccessCount INT DEFAULT 1 ); -- Live Matches CREATE TABLE LiveMatches ( Id INT PRIMARY KEY IDENTITY, Title NVARCHAR(100) NOT NULL, Description NVARCHAR(255), StartTime DATETIME NOT NULL, EndTime DATETIME, StreamUrl NVARCHAR(255) NOT NULL, ThumbnailUrl NVARCHAR(255), IsActive BIT DEFAULT 1 );
Step 3: Backend API Development
Create ASP.NET Core Web API Project
Set up controllers:
AuthController (Login/Register)
SubscriptionController
StreamingController
MatchController
AuthController.cs
[ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly IAuthService _authService; public AuthController(IAuthService authService) { _authService = authService; } [HttpPost("register")] public async Task<IActionResult> Register(RegisterDto registerDto) { var result = await _authService.Register(registerDto); if (!result.Success) return BadRequest(result); return Ok(result); } [HttpPost("login")] public async Task<IActionResult> Login(LoginDto loginDto) { var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); var deviceId = Request.Headers["Device-Id"].ToString(); var result = await _authService.Login(loginDto, ipAddress, deviceId); if (!result.Success) return Unauthorized(result); return Ok(result); } }
StreamingController.cs (with Geo-Restriction)
[ApiController] [Route("api/[controller]")] [Authorize] public class StreamingController : ControllerBase { private readonly IStreamingService _streamingService; private readonly IGeoRestrictionService _geoService; public StreamingController(IStreamingService streamingService, IGeoRestrictionService geoService) { _streamingService = streamingService; _geoService = geoService; } [HttpGet("stream/{matchId}")] public async Task<IActionResult> GetStream(int matchId) { var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); // Check if IP is from India if (!await _geoService.IsAccessAllowed(ipAddress)) { return Forbid("Streaming is only available in India"); } var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value); var streamInfo = await _streamingService.GetStreamUrl(matchId, userId); if (!streamInfo.Success) return BadRequest(streamInfo); return Ok(streamInfo); } [HttpGet("preview/{matchId}")] public async Task<IActionResult> GetPreview(int matchId) { var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); var deviceId = Request.Headers["Device-Id"].ToString(); // Check if IP is from India if (!await _geoService.IsAccessAllowed(ipAddress)) { return Forbid("Streaming is only available in India"); } var previewInfo = await _streamingService.GetPreviewStream(matchId, ipAddress, deviceId); if (!previewInfo.Success) return BadRequest(previewInfo); return Ok(previewInfo); } }
GeoRestrictionService.cs
public class GeoRestrictionService : IGeoRestrictionService { private readonly HttpClient _httpClient; private readonly IConfiguration _config; public GeoRestrictionService(HttpClient httpClient, IConfiguration config) { _httpClient = httpClient; _config = config; } public async Task<bool> IsAccessAllowed(string ipAddress) { try { // Use IP geolocation service (e.g., ipapi.co, ipgeolocation.io) var apiKey = _config["GeoLocation:ApiKey"]; var response = await _httpClient.GetFromJsonAsync<IpGeoLocation>($"http://api.ipapi.com/{ipAddress}?access_key={apiKey}"); return response?.CountryCode == "IN"; } catch { // Fallback - deny access if geolocation fails return false; } } }
Step 4: MAUI Frontend Development
App Structure
Views:
LoginPage.xaml
RegisterPage.xaml
DashboardPage.xaml
MatchDetailPage.xaml
VideoPlayerPage.xaml
SubscriptionPage.xaml
ProfilePage.xaml
ViewModels for each page
Services:
AuthService
StreamingService
SubscriptionService
AuthService.cs (MAUI)
public class AuthService : IAuthService { private readonly HttpClient _httpClient; private readonly IDeviceInfo _deviceInfo; public AuthService(HttpClient httpClient, IDeviceInfo deviceInfo) { _httpClient = httpClient; _deviceInfo = deviceInfo; } public async Task<AuthResponse> Login(string username, string password) { try { var deviceId = _deviceInfo.Idiom.ToString() + _deviceInfo.Model; var request = new LoginRequest { Username = username, Password = password, DeviceId = deviceId }; var response = await _httpClient.PostAsJsonAsync("api/auth/login", request); if (response.IsSuccessStatusCode) { var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>(); // Store token securely await SecureStorage.SetAsync("jwt_token", authResponse.Token); return authResponse; } var error = await response.Content.ReadFromJsonAsync<AuthResponse>(); return error; } catch (Exception ex) { return new AuthResponse { Success = false, Message = ex.Message }; } } // Similar implementation for Register }
VideoPlayerPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="CricketStreaming.VideoPlayerPage" Title="Live Streaming"> <Grid> <MediaElement x:Name="videoPlayer" ShouldShowPlaybackControls="True" ShouldAutoPlay="True" Aspect="AspectFit" /> <ActivityIndicator x:Name="loadingIndicator" IsRunning="True" IsVisible="False" VerticalOptions="Center" HorizontalOptions="Center" /> <Label x:Name="errorLabel" Text="Error loading stream" IsVisible="False" HorizontalOptions="Center" VerticalOptions="Center" /> </Grid> </ContentPage>
VideoPlayerPage.xaml.cs
public partial class VideoPlayerPage : ContentPage { private readonly IStreamingService _streamingService; private int _matchId; public VideoPlayerPage(IStreamingService streamingService, int matchId) { InitializeComponent(); _streamingService = streamingService; _matchId = matchId; LoadStream(); } private async void LoadStream() { loadingIndicator.IsVisible = true; var result = await _streamingService.GetStreamUrl(_matchId); if (result.Success) { videoPlayer.Source = result.StreamUrl; videoPlayer.IsVisible = true; } else { errorLabel.Text = result.Message; errorLabel.IsVisible = true; } loadingIndicator.IsVisible = false; } }
Step 5: Streaming Infrastructure
Option 1: Using Wowza Streaming Engine
Set up Wowza Streaming Engine on a cloud server
Configure live streams for cricket matches
Create different renditions (480p, 720p, 1080p)
Set up HLS/DASH output
Option 2: FFmpeg-based Solution (Open Source)
# Sample FFmpeg command to create multi-bitrate HLS streams ffmpeg -i input_stream -map 0:v:0 -map 0:a:0 \ -c:v libx264 -crf 22 -preset veryfast -g 60 -sc_threshold 0 \ -b:v:0 800k -maxrate:0 856k -bufsize:0 1200k -filter:v:0 "scale=-2:480" \ -b:v:1 1200k -maxrate:1 1280k -bufsize:1 1600k -filter:v:1 "scale=-2:720" \ -b:v:2 2400k -maxrate:2 2560k -bufsize:2 3200k -filter:v:2 "scale=-2:1080" \ -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \ -f hls -hls_time 6 -hls_list_size 0 -hls_playlist_type event \ -hls_segment_filename "stream_%v/segment%d.ts" \ -master_pl_name master.m3u8 \ "stream_%v/index.m3u8"
Sample Streaming Service
public class StreamingService : IStreamingService { private readonly HttpClient _httpClient; private readonly ISubscriptionService _subscriptionService; public StreamingService(HttpClient httpClient, ISubscriptionService subscriptionService) { _httpClient = httpClient; _subscriptionService = subscriptionService; } public async Task<StreamResponse> GetStreamUrl(int matchId) { try { // Check user's subscription var subscription = await _subscriptionService.GetCurrentSubscription(); if (subscription == null || !subscription.IsActive) { return new StreamResponse { Success = false, Message = "You need an active subscription to watch this stream" }; } // Get match stream info from API var response = await _httpClient.GetAsync($"api/streaming/stream/{matchId}"); if (response.IsSuccessStatusCode) { var streamInfo = await response.Content.ReadFromJsonAsync<StreamInfo>(); // Select appropriate stream based on subscription string streamUrl = subscription.Plan.VideoQuality switch { "480p" => streamInfo.Stream480pUrl, "720p" => streamInfo.Stream720pUrl, "1080p" => streamInfo.Stream1080pUrl, _ => streamInfo.Stream480pUrl }; return new StreamResponse { Success = true, StreamUrl = streamUrl }; } var error = await response.Content.ReadFromJsonAsync<StreamResponse>(); return error; } catch (Exception ex) { return new StreamResponse { Success = false, Message = ex.Message }; } } public async Task<StreamResponse> GetPreviewStream(int matchId) { // Similar implementation with 5-minute preview logic } }
Step 6: Subscription and Payment Integration
SubscriptionPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="CricketStreaming.SubscriptionPage" Title="Subscription Plans"> <ScrollView> <StackLayout Padding="20" Spacing="20"> <Label Text="Choose Your Plan" FontSize="Title" HorizontalOptions="Center" /> <!-- Plan A --> <Frame BorderColor="LightGray" CornerRadius="10" Padding="15"> <StackLayout Spacing="10"> <Label Text="Plan A" FontSize="Subtitle" FontAttributes="Bold" /> <Label Text="480p Streaming Quality" /> <Label Text="₹199/month" FontAttributes="Bold" /> <Button Text="Subscribe" BackgroundColor="#4CAF50" TextColor="White" Command="{Binding SubscribeCommand}" CommandParameter="1" /> </StackLayout> </Frame> <!-- Plan B --> <Frame BorderColor="LightGray" CornerRadius="10" Padding="15"> <StackLayout Spacing="10"> <Label Text="Plan B" FontSize="Subtitle" FontAttributes="Bold" /> <Label Text="720p Streaming Quality" /> <Label Text="₹399/month" FontAttributes="Bold" /> <Button Text="Subscribe" BackgroundColor="#2196F3" TextColor="White" Command="{Binding SubscribeCommand}" CommandParameter="2" /> </StackLayout> </Frame> <!-- Plan C --> <Frame BorderColor="LightGray" CornerRadius="10" Padding="15"> <StackLayout Spacing="10"> <Label Text="Plan C" FontSize="Subtitle" FontAttributes="Bold" /> <Label Text="1080p Streaming Quality" /> <Label Text="₹699/month" FontAttributes="Bold" /> <Button Text="Subscribe" BackgroundColor="#9C27B0" TextColor="White" Command="{Binding SubscribeCommand}" CommandParameter="3" /> </StackLayout> </Frame> </StackLayout> </ScrollView> </ContentPage>
Payment Integration (Razorpay for India)
public class PaymentService : IPaymentService { private readonly HttpClient _httpClient; public PaymentService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<PaymentResponse> CreateOrder(decimal amount, string currency, string receipt) { try { var request = new { amount = amount * 100, // Razorpay expects amount in paise currency, receipt, payment_capture = 1 }; var response = await _httpClient.PostAsJsonAsync("api/payment/createorder", request); if (response.IsSuccessStatusCode) { return await response.Content.ReadFromJsonAsync<PaymentResponse>(); } var error = await response.Content.ReadFromJsonAsync<PaymentResponse>(); return error; } catch (Exception ex) { return new PaymentResponse { Success = false, Message = ex.Message }; } } public async Task<PaymentResponse> VerifyPayment(string paymentId, string orderId, string signature) { // Implementation to verify payment with backend } }
Step 7: Deployment
Backend Deployment (Azure/AWS)
Set up Azure App Service or AWS Elastic Beanstalk
Configure SQL Database
Set up Wowza/FFmpeg streaming server
Configure CDN for streaming content
MAUI App Deployment
Android:
Generate signed APK/AAB
Upload to Google Play Store
iOS:
Archive in Visual Studio
Distribute via App Store Connect
Step 8: Testing
Test all user flows:
Registration/Login
Subscription purchase
Geo-restriction (use VPN to test)
Preview functionality
Different quality streams based on plan
Test on multiple devices (Android/iOS)
Final Notes
For a production app, consider:
DRM protection for streams
More robust geo-blocking
Better error handling and logging
Analytics integration
Push notifications for match alerts
For the sample project, you can use:
Mock streaming URLs (public HLS streams)
Simulated payment gateway
Local database for testing
This comprehensive solution provides all the components needed to build a cricket streaming app with the specified requirements. The implementation can be adjusted based on specific needs and available resources.
Comments
Post a Comment