TaherSoft
Farakav · Senior Frontend Developer

anten.ir — live sports streaming

Technology recommendations, Scrum, well-tested documented code, heavy SEO work, and a custom professional player for live streams and archives.

Duration
Approximate date10 mo
Sites on one player
2
01Context

Context

Farakav is an Iranian media-technology company. anten.ir streams live sport — football first [draft] — to an audience that peaks on match nights and watches mostly on mid-range Android phones over mobile data [draft]. heyatonline.ir streams religious ceremonies live, peaking every Muharram [draft], and lets organisers archive and publish their programmes. I joined heyatonline part-time in October 2021 and moved to anten.ir full-time in May 2022, reporting to the frontend lead [draft]. Both frontends were Next.js; the backends spoke SignalR and GraphQL [draft].

Situation. On anten.ir the player was the product, and it was a stock Video.js with a stack of plugins [draft]: it behaved differently on iOS Safari and on cheap Android browsers, live and archive were two different components, and playback failures were invisible — no event reached analytics [draft]. Search was the other gap: match and programme pages were client-rendered lists with duplicate URLs and no structured data, so Google indexed the home page and little else [draft].

On heyatonline the organisers’ panel polled for archive status and drifted, and a set of regulator-driven UI rules (content-classification notices, accessibility) [draft] had to be applied consistently. The constraint that mattered most was the calendar: big matches and ceremonies are fixed dates, and a bad release lands in front of the largest audience of the year.

02What I did

What I did

  1. Built one custom player for live and archive. A Video.js-based component with HLS quality switching, DVR seek on live, Persian UI, keyboard shortcuts, picture-in-picture, a “starts at…” pre-match state, and reconnect with backoff on manifest errors [draft]. Every failure fires an event to analytics. Alternatives rejected: raw hls.js (more control, no time for a UI from scratch) and a commercial player (cost, no RTL, no control over iOS quirks) [draft].
  2. Made the SEO structural, not cosmetic. Match and programme pages moved to server rendering in Next.js, one canonical URL per event, SportsEvent / VideoObject JSON-LD [draft], daily sitemaps, OG images per match, Persian slugs, and Core Web Vitals work on the poster image and fonts [draft]. Alternative rejected: pre-rendering with a headless browser — fragile and a second system to run.
  3. Wired SignalR into Redux on heyatonline. A middleware turns SignalR events into Redux actions, so archive status (“recording”, “processing”, “published”) updates live without polling; the same pattern later drove anten.ir’s match state [draft]. Shipped the organisers’ panel as a PWA so it installs on their phones [draft].
  4. Built the design system with the designer. Tokens in Figma mirrored one-to-one in the MUI theme object; I designed some internal pages myself when the designer was the bottleneck (CV).
  5. Applied the compliance rules once, in components. Notices, classification badges and accessibility rules lived in shared components rather than per page [draft].
  6. Pushed for tests and written decisions. Component tests with Testing Library, a Playwright smoke test on the player, and written recommendations for RTK Query and the GraphQL client that the team adopted [draft]. Scrum with Jira and Zephyr.

Results (confidence in parentheses)

  • One player component serving live and archive on both sites (exact, CV)
  • Playback failures went from unmeasured to measured; iOS failure rate down [N %] after the reconnect logic (verify, draft)
  • Organic visits to match pages up [N %] within [3 months] of server rendering (verify, draft)
  • Archive status on the organisers’ panel: from polling every [30 s] to live (approx, draft)
  • [N] concurrent viewers on [the derby] without a player-side incident after the fix below (verify, draft)
  • Part-time → full-time inside seven months (exact, CV)
Duration
Approximate date10 mo
Sites on one player
2
03What went right

What went right

  • One player, two products. The live/archive split disappeared and every fix landed on both sites at once.
  • Server rendering did what it said. New match pages were indexed within hours instead of days [draft].
  • The SignalR middleware became a house pattern. Other screens adopted it without help.
  • Tokens in Figma = tokens in code. The designer and I stopped arguing about greys. Credit to the designer who kept the Figma file honest.
  • Measuring failures changed the conversation. Once the player reported errors, “it works on my phone” stopped ending discussions.
04What went wrong

What went wrong

Blameless retro4 parts

Timeline

[draft — hypothesis] [A derby night, month N]: a player release that changed autoplay behaviour went live the afternoon of the biggest match of the season. iOS Safari blocks autoplay with sound; iOS viewers saw a black frame and no play button. Noticed within minutes through the Telegram support channel; hotfix (muted autoplay + tap-to-unmute) live in [40 minutes].

Causes

  • No iOS device in the test matrix; QA and the team tested on Android and desktop.
  • The release was scheduled on a match day because the calendar was not part of the release process.
  • The player had no feature flag or kill switch; rollback was a full deploy.
  • The failure was silent for the first minutes because the “autoplay blocked” error event did not exist yet.
  • Browser autoplay policies were known to individuals, not written anywhere.

Impact

iOS viewers lost [N] minutes of the match; support was flooded; the team’s credibility took a hit on the night everyone was watching; a match-day rule was born.

Detection & response: Telegram support → frontend on-call → hotfix branch; then the device matrix and the flag.

Actions

  • Match-day and ceremony-day release freeze — frontend lead
  • iOS Safari and low-end Android in the Playwright matrix via a device lab [draft] — frontend
  • Kill switch and percentage rollout for player changes — frontend
  • Player error events, including “autoplay blocked”, to analytics with an alert on rate — frontend + data
  • A one-page “browser media policies” doc

Second retro item [draft]: the compliance rules on heyatonline arrived after the first panel screens were designed, so several pages were rebuilt. Cause: the rule list lived with another team and was requested late. Action: ask for the regulator’s list in week one and build it into the shared components before the first page.

05If I went back

If I went back

  • KEEPThe single player component, the SignalR middleware and the Figma-to-theme token mirror
  • CHANGESet up the device matrix — a real iPhone and a cheap Android — before touching the player
  • CHANGEFeature flag and percentage rollout before the first player change, not after the first incident
  • CHANGEPut the match calendar into the release process on day one
  • CHANGEAsk for the compliance list before the first screen
06Lessons

Lessons

  • A player that cannot report its own failures is not done.
  • Structural SEO (server rendering, canonical URLs, structured data) beats every meta-tag trick — and it is a backend-shaped job for a frontend engineer.
  • Shared components are where compliance rules belong, so they are applied once.

More with React