Home

Core API

Framework-agnostic API for fetching media, sending analytics, and managing context

The @rixl/media package exports the framework-agnostic core of the SDK. You can use it directly in any JavaScript or TypeScript app, or build your own UI layer on top of it.

Authentication

All core API calls share the @rixl/sdk HTTP client. If your host app already calls connect() from @rixl/sdk, the media core will reuse that authenticated client automatically.

For standalone apps, call connect() once before making any API calls:

import {connect} from "@rixl/media";

await connect({
  baseUrl: "https://api.rixl.com",
  apiKey: "your-rixl-api-key",
});

connect is re-exported from @rixl/media for convenience. It accepts a ConnectConfig object with baseUrl and apiKey. It also seeds the analytics context (device, session, locale, etc.) so the first analytics flush can prepend a session_start event.

Fetching Media

Images

import {getImage} from "@rixl/media";

const image = await getImage("your-image-id");
console.log(image.file.url);

Videos

import {getVideo} from "@rixl/media";

const video = await getVideo("your-video-id");
console.log(video.file.url, video.poster.file.url);

Feed Posts

import {getFeedPost, getFeedPosts} from "@rixl/media";

// Single post
const post = await getFeedPost("feed-id", "post-id");

// Paginated list
const page = await getFeedPosts("feed-id", 0, 10);
console.log(page.posts, page.total, page.offset, page.limit);

getImage, getVideo, getFeedPost, and getFeedPosts cache results and deduplicate in-flight requests automatically.

Analytics

Context and session setup

Call initializeAnalyticsContext() once before emitting events, or rely on connect() which calls it for you. The context stores (analyticsContextStore, analyticsDeviceStore, analyticsSessionStore) hold device_id, session_id, country, language, browser, os, device_type, and screen_resolution.

import {
  initializeAnalyticsContext,
  analyticsContextStore,
  analyticsDeviceStore,
  analyticsSessionStore,
} from "@rixl/media";

initializeAnalyticsContext();

const deviceState = analyticsDeviceStore.get();
const sessionState = analyticsSessionStore.get();

// Optional: merge overrides for a specific page or test
analyticsContextStore.set({...analyticsContextStore.get(), page: "feed", feed_id: "my-feed"});

analyticsContextStore is a nanostores writable atom. Use .set() to replace its value or .get() to read it. setAnalyticsContext(overrides) is also exported to merge partial updates.

Posting events

The simplest way to send analytics events is through the typed helpers. Each helper builds the event, enqueues it for the next flush, and returns the constructed event.

import {postContentEvent, postEngagement, postInteraction, postError} from "@rixl/media";

postContentEvent({
  contentId: "video-id",
  contentType: "video",
  viewType: "watch",
  page: "standalone",
  watchDurationMs: 5000,
  videoPositionMs: 1000,
  videoTotalDurationMs: 120000,
  segments: [{start_ms: 1000, end_ms: 6000, speed: 1}],
});

postInteraction({
  interactionType: "play",
  resourceId: "video-id",
  resourceType: "video",
  page: "standalone",
});

postEngagement({
  engagementType: "share",
  resourceId: "video-id",
  resourceType: "video",
  sharePlatform: "twitter",
});

postError({
  errorType: "video_load_error",
  errorMessage: "Network request failed",
  resourceId: "video-id",
  resourceType: "video",
});

You can also build a low-level array and pass it to postAnalytics:

import {postAnalytics, type ContentViewEvent} from "@rixl/media";

const event: ContentViewEvent = {
  _type: "content_views",
  content_id: "video-id",
  content_type: "video",
  view_type: "watch",
  watch_duration_ms: 15000,
  page: "standalone",
  video_position_ms: 5000,
  video_total_duration_ms: 120000,
  segments: [{start_ms: 5000, end_ms: 20000, speed: 1}],
};

await postAnalytics([event]);

postAnalytics queues events and flushes them in a micro-task. The first flush automatically prepends a session_start event. Failed flushes retry up to three times.

Flush on demand

import {flushAnalytics} from "@rixl/media";

await flushAnalytics();

Heatmap

import {getVideoHeatmap} from "@rixl/media";

const heatmap = await getVideoHeatmap({videoId: "your-video-id", buckets: 101});
console.log(heatmap?.data);

Hot Segments

import {getHotSegments} from "@rixl/media";

const segments = await getHotSegments({
  videoId: "your-video-id",
  startDate: "2025-01-01",
  endDate: "2025-01-31",
});

for (const s of segments) {
  console.log(s.start_second, s.end_second, s.multiplier);
}

TypeScript Interfaces

interface ConnectConfig {
  baseUrl: string;
  apiKey: string;
}

declare function connect(config: ConnectConfig): Promise<void>;

declare function getImage(imageId: string): Promise<ImageData>;
declare function getVideo(videoId: string): Promise<VideoData>;
declare function getFeedPost(feedId: string, postId: string): Promise<FeedPost>;
declare function getFeedPosts(feedId: string, offset?: number, limit?: number): Promise<FeedPostsResponse>;

declare function initializeAnalyticsContext(): void;
declare function postContentEvent(params: BuildEventParams): ContentViewEvent;
declare function buildVideoAnalyticsPayload(params: BuildVideoAnalyticsPayloadParams): VideoAnalyticsPayload;
declare function postEngagement(params: BuildEngagementParams): EngagementEvent;
declare function postInteraction(params: BuildInteractionParams): InteractionEvent;
declare function postError(params: BuildErrorParams): ErrorEvent;
declare function postAnalytics(events: AnalyticsEvent[]): Promise<void>;
declare function flushAnalytics(): Promise<void>;

interface GetVideoHeatmapOptions {
  videoId: string;
  buckets?: number;
}

declare function getVideoHeatmap(options: GetVideoHeatmapOptions): Promise<VideoHeatmap | null>;

interface GetHotSegmentsOptions {
  videoId: string;
  startDate?: string;
  endDate?: string;
}

declare function getHotSegments(options: GetHotSegmentsOptions): Promise<HotSegment[]>;

postContentEvent takes BuildEventParams and buildVideoAnalyticsPayload takes BuildVideoAnalyticsPayloadParams. Millisecond durations are converted to GoogleProtobufDuration strings (e.g. "5s", "1.234s") and content_type is sent as MEDIA_TYPE_IMAGE or MEDIA_TYPE_VIDEO on the wire to /analytics/v1/events.