Tapemetric

Live events / Sports

Live sports — cricket, football, and more

Phase-aware analytics, score and latency auto-stamping, and a markers timeline that shows every wicket, goal, and key moment.

Concept: the active match context

Calling startLiveMatch(ctx) on the SDK installs a match context that the SDK stamps onto every event you emit until you call stopLiveMatch(). This is how the dashboard correlates a buffer event with the over it happened in, or a play_start with the current score. You only set the context once.

typescript
type LiveMatchContext = {
  matchId: string;        // = the match_id you registered + content_id you send
  phase?: string;         // 'innings_1', 'half_time', etc — see below
  matchClockSec?: number; // match-time, not wall-clock
  scoreA?: number;        // cricket: runs / football: goals (home)
  scoreB?: number;        // football: goals (away)
  getLatencyMs?: () => number | undefined; // sampled per event
};

Phases

The phase field is free-form so it works across sports, but here are the recommended values:

SportPhases
Cricketpre_match, innings_1, innings_break, innings_2, super_over, post_match
Footballpre_match, first_half, half_time, second_half, et_first, et_second, penalties, post_match
Other sportsUse whatever segments make sense — q1 through q4 for basketball, set_1 / set_2 / set_3 for tennis, etc.

Cricket

Use trackCricketEvent for moments that should appear as markers on the dashboard timeline. The score and overs are recorded so the marker tooltip can show them.

typescript
import Tapemetric from '@tapemetric/analytics';

const tm = Tapemetric.init({ apiKey: 'tm_live_yourkey' });

// Viewer joins
tm.startLiveMatch({
  matchId: 'ipl_2026_csk_vs_mi',
  phase: 'innings_1',
  scoreA: 0,
  scoreB: 0,
  getLatencyMs: () => Math.round((hls.liveSyncPosition - video.currentTime) * 1000),
});

tm.trackPlayStart(
  { contentId: 'ipl_2026_csk_vs_mi', contentType: 'live' },
  { positionSec: 0 }
);

// Score updates from your scoring feed (every ball)
tm.updateLiveMatch({ scoreA: 47, matchClockSec: 540 });

// A wicket
tm.trackCricketEvent({
  kind: 'wicket',
  label: 'Kohli c. Smith b. Cummins (24)',
  runs: 87,
  wickets: 3,
  overs: 8.4,
});

// A six
tm.trackCricketEvent({
  kind: 'six',
  label: 'Hardik over long-on',
  runs: 145,
  wickets: 4,
  overs: 14.2,
});

// Phase change at innings break
tm.updateLiveMatch({ phase: 'innings_break' });

tm.stopLiveMatch();

Football

typescript
tm.startLiveMatch({
  matchId: 'isl_2026_final',
  phase: 'first_half',
  scoreA: 0,
  scoreB: 0,
});

tm.trackPlayStart(
  { contentId: 'isl_2026_final', contentType: 'live' },
  { positionSec: 0 }
);

// Goal at minute 23
tm.trackFootballEvent({
  kind: 'goal',
  label: "GOAL — Sunil Chhetri (23')",
  minute: 23,
  homeScore: 1,
  awayScore: 0,
});

// Half-time
tm.trackFootballEvent({
  kind: 'half_time',
  label: 'HT 1-0',
  minute: 45,
});
tm.updateLiveMatch({ phase: 'half_time' });

tm.stopLiveMatch();

iOS

swift
import TapemetricAnalytics

let ctx = LiveMatchContext(
    matchId: "ipl_2026_csk_vs_mi",
    phase: "innings_1",
    scoreA: 0,
    scoreB: 0,
    getLatencyMs: { [weak self] in
        guard let player = self?.player else { return nil }
        return Int(player.liveLatencySeconds * 1000)
    }
)
Tapemetric.shared.startLiveMatch(ctx)

Tapemetric.shared.updateLiveMatch(scoreA: 47, matchClockSec: 540)

Tapemetric.shared.trackCricketEvent(
    kind: "wicket",
    label: "Kohli c. Smith b. Cummins (24)",
    runs: 87,
    wickets: 3,
    overs: 8.4
)

Tapemetric.shared.stopLiveMatch()

Android

kotlin
import com.tapemetric.analytics.Tapemetric
import com.tapemetric.analytics.LiveMatchContext

val ctx = LiveMatchContext(
    matchId = "ipl_2026_csk_vs_mi",
    phase = "innings_1",
    scoreA = 0,
    scoreB = 0,
    getLatencyMs = { exoPlayer.currentLiveOffset.toInt() }
)
Tapemetric.startLiveMatch(ctx)

Tapemetric.updateLiveMatch(scoreA = 47, matchClockSec = 540)

Tapemetric.trackCricketEvent(
    kind = "wicket",
    label = "Kohli c. Smith b. Cummins (24)",
    runs = 87,
    wickets = 3,
    overs = 8.4
)

Tapemetric.stopLiveMatch()

Latency tracking

The optional getLatencyMs callback is invoked on every event the SDK emits. For HLS players this is typically (liveSyncPosition - currentTime) * 1000. Each event then carries an accurate live-edge measurement which the QoS endpoint percentile-bins for the match-detail dashboard. Viewers more than target_latency_sec behind are flagged so support can investigate regional CDN drift.

The latency callback is wrapped in a try/catch by the SDK. A buggy callback won’t break event capture — the event is sent without the latency field.

Producer markers via API

If your scoring or production team works in software you control, you can push markers directly via API instead of through the SDK. This is sometimes easier — your scoring desk doesn’t need the SDK in their workflow.

bash
POST /v1/live/matches/{match_id}/markers
Authorization: Bearer eyJ...

{
  "ts": "2026-04-30T20:14:30+05:30",
  "kind": "wicket",
  "label": "Kohli c. Smith b. Cummins (24)",
  "properties": { "runs": 87, "wickets": 3, "overs": 8.4 }
}

Operational best practices

  • Pre-warm the dashboard. Open the match detail page 15 minutes before tip-off so you can see traffic ramping.
  • Set realistic latency targets. 30 seconds is fine for HLS; LL-HLS gets you under 10. Don’t set target_latency_sec below what your encoder actually produces.
  • Throttle score updates. Cricket scoring updates every ball is fine; calling updateLiveMatch 100× per second from a glitchy feed will overwhelm the queue. Once per ball or once per minute is the sweet spot.
  • Mark ad breaks. Use kind: 'ad_break_start' and 'ad_break_end' so the concurrency dip in the curve is explained, not alarming.
  • Tag the phase change. Always update the phase field at major transitions (innings break, half-time). The audience tab uses it to compute "held through halftime" metrics.
  • Don’t reuse match_ids. If you replay a match later (highlight cut), give the highlight a different ID — otherwise events from both will collide on the same dashboard.