> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindcast.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Basic Usage

> Create a player, load a manifest, listen to events, read metrics, and clean up when done.

## Create a player

`createPlayer` returns a `Result<BlindcastPlayer, PlayerError>`. The video element must be in the DOM before you call it.

```typescript theme={null}
import { createPlayer } from "@blindcast/player"

const videoEl = document.getElementById("video") as HTMLVideoElement

const result = createPlayer(videoEl, {
  keyServerUrl: "https://keys.example.com/keys",
})

if (!result.ok) {
  console.error(result.error.code, result.error.message)
  return
}

const player = result.value
```

<Note>
  `keyServerUrl` must include the `/keys` path — e.g., `https://your-worker.workers.dev/keys`, not just the base URL. The player appends `/:contentId` to this path when fetching keys.
</Note>

## Load a manifest

```typescript theme={null}
player.load("https://cdn.example.com/content/my-video/manifest.m3u8")
```

The player fetches the manifest, reads `EXT-X-KEY` tags to discover the key server URL, fetches the content key, then decrypts each segment as it downloads.

## Events

Subscribe to events with `player.on()`. It returns an unsubscribe function.

```typescript theme={null}
const unsub = player.on("ready", ({ levels }) => {
  console.log(`Ready — ${levels} quality levels`)
})

player.on("playing", () => console.log("Playing"))
player.on("paused", () => console.log("Paused"))
player.on("buffering", () => console.log("Buffering..."))
player.on("ended", () => console.log("Ended"))

player.on("keyLoaded", ({ contentId, epoch }) => {
  console.log(`Key loaded for ${contentId}, epoch ${epoch ?? "none"}`)
})

player.on("qualitySwitch", ({ fromLevel, toLevel }) => {
  console.log(`Quality: ${fromLevel} → ${toLevel}`)
})

player.on("error", (err) => {
  console.error(`[${err.code}] ${err.message}`)
})

player.on("destroyed", () => {
  console.log("Player destroyed")
})

// Remove a specific listener
unsub()
```

## Metrics

Call `getMetrics()` at any time for a snapshot of cumulative stats since `load()`.

```typescript theme={null}
const m = player.getMetrics()

console.log(`TTFF: ${m.timeToFirstFrame}ms`)
console.log(`Decrypt avg: ${m.avgDecryptTime}ms`)
console.log(`Fragments loaded: ${m.fragmentsLoaded}`)
console.log(`Stalls: ${m.stallCount}`)
```

Useful for dashboards, debugging, and monitoring. See [Monitoring](/production/monitoring) for production recommendations.

## Destroy

Always call `destroy()` when the component unmounts or the user navigates away.

```typescript theme={null}
player.destroy()
// Stops playback, detaches hls.js, clears the key cache, emits "destroyed"
```

### React example

```tsx theme={null}
import { useEffect, useRef } from "react"
import { createPlayer, isPlayerSupported } from "@blindcast/player"

function VideoPlayer({ manifestUrl }: { manifestUrl: string }) {
  const videoRef = useRef<HTMLVideoElement>(null)

  useEffect(() => {
    if (!videoRef.current || !isPlayerSupported()) return

    const result = createPlayer(videoRef.current, {
      keyServerUrl: "https://keys.example.com/keys",
    })
    if (!result.ok) return

    const player = result.value
    player.load(manifestUrl)

    return () => player.destroy()
  }, [manifestUrl])

  return <video ref={videoRef} controls />
}
```
