@norskvideo/norsk-player
v1.2.1
Published
A browser-native, frame-accurate video player custom element (<norsk-video>) for content served from a TAMS (Time-addressable Media Store).
Downloads
418
Readme
@norskvideo/norsk-player
A browser-native, frame-accurate video player for content served from a TAMS store.
norsk-player ships as a custom element —
<norsk-video> — that you drop into any web page. It decodes media in the
browser with WebCodecs,
lets you scrub and step through footage a single frame at a time, and reads
media directly from a TAMS API rather than from a single monolithic file. It
works with any framework, or none. A React wrapper is also available.
The player is designed for, and tested against, Norsk's TAMS implementation, but it targets the open TAMS API and works against any conforming store.
What is TAMS?
TAMS — Time-addressable Media Store — is an open API, originally developed by the BBC, for storing and retrieving media as a timeline of segments rather than as fixed files. A client asks for a source, over a time range and the store returns the media objects that cover it. This makes it a natural fit for growing/live recordings, instant replay, and frame-accurate navigation of long-form content.
- Spec & docs: https://bbc.github.io/tams/main/index.html
- Overview & app notes: https://github.com/bbc/tams
You do not need to understand the TAMS API to use this player — you point it at
a URL and it does the rest. The TAMS client is
also exported if you want to query the store yourself.
Installation
npm install @norskvideo/norsk-playerThe package is an ES module. It bundles everything it needs, including Media Chrome for the player controls — no separate install required. You will typically consume it through a bundler such as Vite, Webpack, or Parcel.
New to web components?
A web component (custom element) is a reusable HTML tag backed by
JavaScript, built into every modern browser — no framework needed. Importing
this package registers the <norsk-video> tag (and the Media Chrome
control elements) with the browser. After the import runs once, you can use
those tags in your HTML just like <video> or <div>:
import '@norskvideo/norsk-player';
// <norsk-video> is now a valid element anywhere on the page.<norsk-video> renders the video surface but no controls of its own — you
pair it with a <media-controller> (from the bundled Media Chrome) to get a
play button, scrubber, volume, fullscreen, and so on. The example below shows
the whole thing wired up.
Get started
The quickest path is a Vite project.
1. Create a project and add the player
npm create vite@latest my-player -- --template vanilla
cd my-player
npm install @norskvideo/norsk-player2. Replace main.js with:
import '@norskvideo/norsk-player';3. Put the player in index.html, inside a Media Chrome controller:
<media-controller style="width: 100%; aspect-ratio: 16 / 9;">
<norsk-video
slot="media"
src="http://localhost:8080/x-tams/"
style="width: 100%; height: 100%;"
></norsk-video>
<media-control-bar>
<media-play-button></media-play-button>
<media-time-range></media-time-range>
<media-time-display show-duration></media-time-display>
<media-mute-button></media-mute-button>
<media-volume-range></media-volume-range>
<media-fullscreen-button></media-fullscreen-button>
</media-control-bar>
</media-controller>4. Run it
npm run devPoint src at your own TAMS store (see Configuration). With
the default src above, the player plays the latest audio and video source
the store knows about.
Frame stepping: press
./nto step forward one frame and,/pto step back (the React wrapper wires these keys up for you; in plain HTML, dispatchframe-next/frame-previousevents at the element — see Programmatic control).
Configuration
The src URL
src points at a TAMS store, or at a plain MP4/fMP4 file. The player accepts
several forms:
| src value | Plays |
| --- | --- |
| https://host/x-tams/ | The latest audio and video source in the store |
| https://host/x-tams/sources/{sourceId} | The latest audio and video of a particular source/stream |
| https://host/x-tams/sources/{audioId},https://host/x-tams/sources/{videoId} | The two specific sources given (comma-separated), one audio and one video |
| https://host/path/clip.mp4 (or .fmp4) | A single progressive/fragmented MP4 file, no TAMS involved |
There is also a WebSocket endpoint at …/x-tams/ws that emits events as
sources and flows change — useful for building your own source picker.
Attributes
Set these as HTML attributes on <norsk-video>:
| Attribute | Type | Default | Purpose |
| --- | --- | --- | --- |
| src | string | — | Media source (see above). Required. |
| fillContainer | boolean | false | Fill the container (crop) instead of letterboxing to fit. |
| prefetchAudioMs | number | 2000 | How far ahead, in ms, to prefetch audio. |
| thumbnailWidth | number | 144 | Width of generated scrubber thumbnails. |
| thumbnailHeight | number | 81 | Height of generated scrubber thumbnails. |
| class / style | string | — | Forwarded to the inner <video> element. |
srcis read once when the element is connected. To load a different source, replace the element (e.g. change itskeyin React) rather than mutating the attribute in place.
Authentication
If your TAMS store requires authorization, set the authHeader property
(not an attribute) to a function returning the header value. It is called on
every request, so you can rotate or refresh tokens without re-creating the
player:
const player = document.querySelector('norsk-video');
// Bearer token
player.authHeader = () => `Bearer ${myAccessToken}`;
// or HTTP Basic
player.authHeader = () => `Basic ${btoa(`${user}:${pass}`)}`;Programmatic control
<norsk-video> implements a subset of the standard
HTMLMediaElement
interface, so it drives Media Chrome and behaves much like a <video> element:
const player = document.querySelector('norsk-video');
player.play();
player.pause();
player.currentTime = 12.5; // seconds, relative to the start of playback
player.volume = 0.5; // 0..1
player.muted = true;
player.currentTime; // read position
player.duration; // total length (seconds)
player.paused; // boolean
player.readyState; // HAVE_NOTHING … HAVE_ENOUGH_DATA
player.videoWidth; // intrinsic width
// Absolute media timestamp of playback zero — add it to `currentTime` to get an
// absolute TAMS timestamp, or subtract to seek to one.
player.mediaTimeOrigin;
// Frame-accurate stepping:
player.dispatchEvent(new Event('frame-next'));
player.dispatchEvent(new Event('frame-previous'));It emits the usual media events, which is what lets Media Chrome (or your own
UI) stay in sync: loadstart, loadedmetadata, loadeddata, canplay,
play, playing, pause, ended, waiting, seeking, seeked,
timeupdate, durationchange, progress, volumechange, and error.
Talking to TAMS directly
The package also exports a small typed TAMS client (and the generated API
types under API) if you want to enumerate sources yourself — for example to
build a source picker before choosing a src:
import { TAMS } from '@norskvideo/norsk-player';
const tams = new TAMS('https://host/x-tams/', () => `Bearer ${token}`);
const sources = await tams.getSources();React
If you use React, prefer the wrapper package, which provides a <Player>
component with the Media Chrome control bar already assembled:
npm install @norskvideo/norsk-player-reactimport { Player } from '@norskvideo/norsk-player-react';
<Player
className="w-full h-full"
src="https://host/x-tams/"
authHeader={() => `Bearer ${token}`}
/>;Browser support
The player decodes media in the browser with the WebCodecs and Web Audio
APIs, and must be served from a
secure context
(HTTPS, or localhost). This means a recent Chromium-based browser (Chrome,
Edge) or another browser with WebCodecs support. Check current availability at
caniuse: WebCodecs.
