# Changelog — @nomercy-entertainment/nomercy-video-player

## [Unreleased]

## [2.0.1] — 2026-07-18

First published stable of the v2 line (2.0.0 was withdrawn before release), published under the `latest` dist-tag — a plain `npm install @nomercy-entertainment/nomercy-video-player` now resolves `2.0.1` instead of the v1 `1.2.7`. Built against the stable `@nomercy-entertainment/nomercy-player-core@^2.0.1`. The entries below shipped since `2.0.0-rc.35`; everything earlier is captured in the release-candidate history.

### Changed

- `KeyHandlerPlugin.id` renamed from `video-key-handler` to `key-handler`, matching the core base and the music player. `getPluginById('video-key-handler')` lookups and the `plugin.video-key-handler.speed` translation key move to the `key-handler` spelling; class-based `getPlugin(KeyHandlerPlugin)` callers are unaffected. `TvKeyHandlerPlugin` keeps its distinct `tv-key-handler` id.

### Added

- `./plugins/tv-key-handler` subpath export — `TvKeyHandlerPlugin` no longer requires the `./plugins` barrel.
- `VideoPreloadStrategy` now preloads chapter VTT sidecars (`tracks[].kind === 'chapters'`, category `chapter`) and font manifests (`item.fonts`, category `font`), as its docs already claimed.

### Removed

- `webvtt-parser` + `@types/webvtt-parser` dependencies — declared but never imported; the kit cue parser registry has handled VTT since 2.0.0-beta.0.

## [2.0.0-rc.35] — 2026-07-16

### Added

- `IVideoBackend.bandwidthEstimate?(): number` — an optional backend capability reporting the live stream-engine throughput estimate in bits per second. `Html5VideoBackend` implements it as a pass-through to hls.js's own EWMA `bandwidthEstimate`, returning `0` (never `undefined`/`NaN`) when no HLS instance is bound yet. Native/progressive backends simply omit the method. This is the source-side half of the fix for `bandwidth()` permanently returning `0`: paired with `@nomercy-entertainment/nomercy-player-core@^2.0.0-rc.33`, which resolves the estimate through this new capability. Purely additive — no existing method signature changes. Requires `@nomercy-entertainment/nomercy-player-core@^2.0.0-rc.33` or newer.

## [2.0.0-rc.26] — 2026-07-05

### Changed

- The `time` event now carries core rc.28's full `TimeState` snapshot (`time`, `position`, `duration`, `buffered`, `remaining`, `percentage`) instead of `{ time }` — built from the backend's fresh position via core's `_timeStateAt`, so progress bars and clocks paint straight from the payload. Existing `({ time }) => …` listeners are unaffected. This closes the asymmetry with the music player, which already shipped a five-field payload. Requires `@nomercy-entertainment/nomercy-player-core@^2.0.0-rc.28`.

## [2.0.0-rc.25] — 2026-07-04

### Added

- Translation bundles for three more plugins: `key-handler` (`plugin.video-key-handler.speed`, the playback-rate OSD), `media-session` (`plugin.media-session.season`, the OS media-controls album line, localized per locale — Staffel/シーズン/Сезон/…), and `touch-zones` (the ±seek indicators). Each ships the full locale set and registers via `translationsFromGlob`, same as the existing bundles.
- `plugin.desktop-ui.token.extras` — the season-0 title prefix ("Extras E3") is now a translated token in every desktop-ui locale file AND in the static `token-bundle.ts` seed, so it resolves like the season/episode prefixes even before the plugin's lazy bundle loads. Non-Latin locales get real translations (特典, 스페셜, إضافات, …); languages whose media UIs use "Extras" keep it.
- `backend(kind)` setter overload on `NMVideoPlayer`/`IVideoPlayer`, mirroring the music player's backend surface: disposes the current backend, creates the new one, emits `backend:changed`. Only `'html5'` has a built-in implementation; other kinds route through `backendFactory` or reject with `core:not-implemented/video-backend`.
- `subtitle-size-up` / `subtitle-size-down` are now declared members of `VideoEventMap` (the shared UI-intent pattern used by `display-message`/`back`/`close`), so key-handler subtitle size changes type-check instead of riding the untyped string escape hatch.
- `DesktopUiPlugin` claims sole ownership of the `activity` event via player-core rc.26's `activityTracking(false)` (its own state machine handles menu-open/hover pinning and scrub state), and hands the player's built-in tracker back at teardown. `NMVideoPlayer` declares the new `bumpActivity()` / `activityTracking()` surface, and `IVideoPlayer` gains T-typed `peekNext()` / `peekPrevious()` alongside the existing `item()`/`queue()` declarations. Requires `@nomercy-entertainment/nomercy-player-core@^2.0.0-rc.26`.

### Fixed

- `KeyHandlerPlugin`'s help-overlay key now calls `DesktopUiPlugin.toggleShortcuts()` directly via `player.getPlugin()` instead of hand-forging an event in the desktop-ui plugin's namespace; `TvKeyHandlerPlugin` emits its own events through the plugin's auto-namespacing `emit` instead of hand-built `plugin:tv-key-handler:*` strings. Wire-level event names are unchanged.
- The IIFE CDN bundle build works in the monorepo again: the Vite aliases that map core imports onto its live TypeScript source are now derived from core's exports map, so directory-index subpaths (`plugins/key-handler`) and remapped ones (`streams/*`) resolve exactly like the published package.
- Published tarballs now actually contain `nomercy-video-player.iife.js`: `prepublishOnly` runs `build:all`. Previously the ESM-only rebuild inside `npm publish` wiped `dist/` after CI had built the bundle, so every rc shipped without its CDN artifact.

## [2.0.0-rc.24] — 2026-07-04

### Fixed

- Inherits `nomercy-player-core` 2.0.0-rc.24's consumer volume taper (`position²`) — the −60 dB fader law made everything below ~70% slider barely audible. No video-player source change; the curve lives in the shared `MediaElementBackend`.

## [2.0.0-rc.23] — 2026-07-03

### Changed

- Dropped the duplicate `controls?: boolean` field from `VideoPlayerConfig`; it now inherits from `@nomercy-entertainment/nomercy-player-core`'s `BasePlayerConfig` (shared with the music player — same native `<video controls>` / `<audio controls>` semantics, no domain twist). No consumer-visible behavior change: the field still exists with the same type and default, and `backend()`'s `if (this.options?.controls)` handling is untouched. Requires `@nomercy-entertainment/nomercy-player-core@^2.0.0-rc.22` or newer.

## [2.0.0-rc.22] — 2026-07-02

### Breaking

- Realigned with `nomercy-player-core` rc.21's M1 Connect-plugin slice: `volume()`, `mute()`, `unmute()`, `subtitle()`, `audioTrack()`, `playbackRate()`, `repeatState()`, `shuffleState()`, and `dispose()` now return `Promise<void>` on `NMVideoPlayer` and `IVideoPlayer`, matching `IPlayer`. The published rc.21 of this package still declared the old `void` signatures, which no longer structurally satisfied `IPlayer` and broke typecheck for every `Plugin<NMVideoPlayer, ...>` consumer once core rc.21 was installed alongside it — rc.22 is the fix. Code that read state synchronously right after calling one of these must now `await` the call first; fire-and-forget callers are unaffected. Requires `@nomercy-entertainment/nomercy-player-core@^2.0.0-rc.21` or newer.

### Added

- `V1VideoCompatPlugin` (`import { V1VideoCompatPlugin } from '@nomercy-entertainment/nomercy-video-player'`) — opt-in shim attaching the full v1 method surface onto `NMVideoPlayer` via declaration merging. Every shim delegates to the real v2 API and logs one `@deprecated` warning per call. Add via `player.addPlugin(V1VideoCompatPlugin)` before `setup()`; delete the plugin once migrated.

### Fixed

- The wrapped `dispose()` override now awaits the composed cancellable dispose and only tears down the video backend + registry entry once `phase()` actually reaches `'disposed'`, instead of killing the backend (and any live HLS instance) before `beforeDispose` had a chance to run. A plugin calling `preventDefault()` on `beforeDispose` no longer loses its backend.
- Standalone CI (outside the monorepo checkout) now lints cleanly — the ESLint config imports the player rule pack from `@nomercy-entertainment/nomercy-player-core/eslint-plugin` instead of a monorepo-relative path that doesn't exist in a standalone clone.

## [2.0.0-rc.21] — 2026-07-02

### Changed

- Backend now routes through the shared core helpers (`createAuthorizationXhrSetup`, `destroyHlsInstance`, `bridgeBackendPlayState`); the player's existing play-state behavior (pause-unless-ended, reset on loadstart and emptied) is preserved via options. Internal only; no public surface change.

## [2.0.0-rc.15] — 2026-06-29

### Added

- Real-browser Playwright e2e suite (`e2e/`) covering play/pause icon swap, `.is-active` state for speed and aspect-ratio buttons, `.muted` on the volume button, and PiP icon wiring. Fixtures are generated by the NoMercy ffmpeg fork via `npm run test:e2e` (the `pretest:e2e` step builds them automatically).

### Changed

- State-button consistency: pip, subtitle, quality, and audio buttons now show the `.is-active` indicator when their state is non-default or engaged, consistent with the existing speed and aspect-ratio buttons. Previously the audio button never applied `.is-active`; that is fixed.

### Fixed

- `V1VideoCompatPlugin` now tracks duration per player instance, so multiple players on the same page no longer corrupt each other's v1 `TimeData` payloads.
- `V1VideoCompatPlugin` correctly bridges the v1 `'playlist'` event to the v2 `'queue'` event.

## [2.0.0-rc.14] — 2026-06-29

### Fixed

- `V1VideoCompatPlugin` `load()` and `addTranslations()` shims were silently skipped on construction and never installed. Both shims now register correctly.
- `V1VideoCompatPlugin` bridges the v1 `'playlist'` event to the v2 `'queue'` event so v1 consumers receive queue updates without modification.
- Real-browser e2e harness repaired and extended: fixture generation, test runner wiring, and coverage of additional player interactions.

---

## [2.0.0-rc.13] — 2026-06-28

### Changed

- Version aligned with the player trio. No code changes.

---

## [2.0.0-rc.12] — 2026-06-28

### Fixed

- Mobile tap now toggles the controls UI correctly. The pre-tap visibility is snapshotted on `pointerdown` so a tap from the hidden state shows and holds the controls instead of producing a flash-and-hide.

---

## [2.0.0-rc.11] — 2026-06-28

### Fixed

- The control auto-hide timer re-arms only on user-initiated seeks. Programmatic or relayed seeks no longer trigger the timer, eliminating overlay flicker on non-interactive seeks.

---

## [2.0.0-rc.10] — 2026-06-28

### Fixed

- Fixed the control auto-hide desync where the activity flag and the `.active` class diverged and left controls permanently hidden. `setActivity` now reconciles against the live DOM state before applying a class change.

---

## [2.0.0-rc.9] — 2026-06-28

### Changed

- `hls.js` is no longer a direct dependency of this package. The core package owns the `hls.js` dependency; video pulls it transitively. A `devDependency` entry is retained for types and test fixtures only.

---

## [2.0.0-rc.8] — 2026-06-28

### Fixed

- The published ESM build now emits `.js` extensions on all relative imports via `tsc-alias`. Raw Node ESM consumers (`node --input-type=module`, `require()` shimmed environments) no longer hit bare-specifier resolution failures.

---

## [2.0.0-rc.7] — 2026-06-28

### Changed

- Pinned `@nomercy-entertainment/nomercy-player-core` to `rc.6`.
- Lint runs with `--max-warnings 0`; all warnings in the package are resolved.

### Fixed

- Touch-zones center zone: a single tap always toggles playback regardless of controls visibility, consistent with the documented tap-zone spec.

---

## [2.0.0-rc.6] — 2026-06-14

### Fixed

- The v1-compat plugin now bridges 9 v1 public accessors (getPlaylist, getPlaylistIndex, getSeasons, getGain/setGain,
  getSubtitleFile, getChapterText, getCaptionIndexBy, getAudioTrackIndexByLanguage) that an upgrading v1 consumer
  could call. The plugin once again covers the full v1 public surface, so old
  code keeps working with only the compat plugin added.

## [2.0.0-rc.5] — 2026-06-14

### Changed

- The desktop-ui, tv-key-handler, and cast-sender plugins use a typesafe
  translation key schema: each `en.ts` exports its canonical key type, and every
  language file is full-coverage `satisfies Record<…TranslationKey, string>`
  instead of the loose `Record<string, string>`. All supported languages ship,
  scoped to each plugin's own `i18n` folder.

### Fixed

- The README and the package `homepage` point at the live docs route
  (`docs.nomercy.tv/nomercy-video-player/`) instead of the dead `/player/` path.

## [2.0.0-rc.4] — 2026-06-14

### Fixed

- The standalone build resolves the player core from `node_modules` instead of
  a sibling monorepo checkout, so `build:all` (including the IIFE bundle) runs
  in a clean CI checkout. The vite and vitest configs guard the source aliases
  behind `existsSync` and follow the core's repository rename. The stale
  tsconfig `paths` that pointed at sibling kit/octopus dist were removed.
- Every source file carries the `Apache-2.0` license header.

## [2.0.0-rc.2] — 2026-06-14

### Changed

- The base event map is generic over the item type and emits the universal
  queue-item selection event.
- v1 `getCurrentSrc` and remaining `url` reads no longer cast now that `url` is a
  base field.

### Fixed

- Desktop-ui activity handling is de-duplicated.

### Removed

- Stale `./plugins/embed`, `./plugins/message`, and `./plugins/tab-leader`
  subpath exports that pointed at unbuilt files. These kit plugins remain
  available through the `./plugins` aggregate or directly from the core package.

## [2.0.0-beta.1] — 2026-05-30

### Changed

- Dependency on `@nomercy-entertainment/nomercy-player-core` changed from `file:` local
  path to `^2.0.0-beta.0` semver range — resolves correctly from the npm registry.
- `exports` map corrected: all directory-based plugins (`drm`, `embed`, `key-handler`,
  `live-transcoding`, `media-session`, `message`, `octopus`, `skipper`, `tab-leader`,
  `touch-zones`) now point to `./dist/plugins/<name>/index.js` matching tsc output layout.
- Build pipeline clarified as tsc-only; `vite.config.ts` and `vite.config.iife.ts` marked
  as non-publish reference files.
- `contributors` email updated to GitHub noreply address.

---

## [2.0.0-beta.0] — 2026-05-16

Rebuilt on `@nomercy-entertainment/nomercy-player-core` 2.0.0. The shared player
infrastructure (transport, queue, plugin runtime, auth, i18n, lifecycle) has moved into
the kit. All public API from v1.x is retained — import names are unchanged.

### Added

- `NMVideoPlayer` rebuilt as a mixin-composed class over the kit core
- `Html5VideoBackend` adapter — encapsulates all `<video>` element + HLS.js state;
  swappable via the kit's `stream` adapter port
- HDR-aware ABR: constrains HLS.js level selection to the active display's
  dynamic-range capability; updates live when the window moves to another monitor
- Plugin subpath exports: `./plugins/cast-sender`, `./plugins/desktop-ui`,
  `./plugins/drm`, `./plugins/embed`, `./plugins/key-handler`, `./plugins/live-transcoding`,
  `./plugins/media-session`, `./plugins/message`, `./plugins/octopus`, `./plugins/skipper`,
  `./plugins/subtitle-overlay`, `./plugins/tab-leader`, `./plugins/touch-zones`
- `desktop-ui` plugin: progressive breakpoint system, container queries, vertical playlist
  menu in portrait, double-tap touch seek, chapter buttons, dialog overlay redesign,
  in-popup mute button
- `touch-zones` plugin: debounced controlsVisible state machine, double-tap seek,
  center-tap toggles playback regardless of controls state
- `tv-ui` plugin: TV remote color-button + universal media-key bindings
- `auto-advance` plugin
- `subtitle-overlay` adapter with container-relative sizing and a11y clamp
- `vtt-chapters` and `vtt-sprite` adapters
- `subtitle-style-store` adapter
- `video-backend` adapter port (`./dist/player/video-backend/`)
- HLS.js is now a peer dependency (optional) — ESM-only build, no CJS/UMD outputs
- `sideEffects: false` — full tree-shaking support

### Fixed (from 1.2.7 work, carried into 2.0.0)
- HLS: stale instance destroyed before creating a new one on stream switch
- `resolveUrl` consults `imageBasePath` for poster/cast categories
- MediaSession seeds metadata from existing current item on plugin `use()`
- `currentEpoch` serializes `current()` autoplay to prevent stale loads on rapid
  episode switches
- `load()` rejects on fatal HLS errors before metadata arrives
- Poster applied synchronously to eliminate black-frame gap on source change
- `10s` hard timeout on `waitForLoadedMetadata` to unblock stuck HLS pipelines

### Breaking Changes (v1 → v2)

**Consumer API**

- `seek(t)` renamed to `currentTime(t)` — `seek` is gone with no alias
- `speed(v)` / `speeds()` renamed to `playbackRate(v)` / `playbackRates()`
- `muted(bool)` removed — use `mute()` / `unmute()` / `toggleMute()`
- `quality(idx)` renamed to `currentQuality(idx)`
- `audioTrack(idx)` renamed to `currentAudioTrack(idx)`
- `subtitle(idx)` renamed to `currentSubtitle(idx)`
- `chapter(time)` renamed to `currentChapter()` — v2 reads from current position, no arg
- `fullscreen(v)` renamed to `fullscreenState(v)`
- `pip(v)` renamed to `pipState(v)`
- `theater(v)` renamed to `theaterState(v)`
- `aspect(v)` renamed to `aspectRatio(v)`
- `playlist()` renamed to `queue()`; `setPlaylist(items)` renamed to `queue(items)`
- `playVideo(idx)` renamed to `seekToIndex(idx)`
- `playlistItem(idx)` split into `current()` + `seekToIndex(idx)`
- `playlistIndex()` renamed to `currentIndex()`
- `state()` renamed to `playState()`
- `element()` renamed to `container` (property, no call)
- `buffer()` renamed to `bufferedRanges()`
- `fetchPlaylist(url)` renamed to `loadQueue(url, parser?)`
- `registerPlugin(name, inst)` replaced by `addPlugin(PluginClass, opts?)`
- `usePlugin(name)` removed — plugins activate automatically in `addPlugin`
- `plugin(name)` renamed to `getPluginById(id)`
- `getAccessToken()` replaced by `player.auth()?.bearerToken`
- `setAccessToken(t)` replaced by `player.auth({ bearerToken: t })`
- `localize(key)` renamed to `t(key, vars?)`
- `hasSpeeds()`, `hasQualities()`, `hasAudioTracks()`, `hasSubtitles()` removed — derive from `.length` checks
- `setEpisode(season, ep)` removed — use `queue()` + `current(item)`
- `seasons()` removed — derive from `player.queue()` grouped by `item.season`
- `skippers()` / `skip()` relocated to `SkipperPlugin`
- `gain()` / `addGainNode()` / `removeGainNode()` relocated to `AudioGraphPlugin`
- `displayMessage()` relocated to `MessagePlugin`
- `setMediaAPI()` replaced by `addPlugin(MediaSessionPlugin)`
- `playerUIPlugin` renamed to `DesktopUiPlugin`
- `setTitle()` removed — consumer concern; player must not touch `document.title`
- `float(v)` removed — consumer viewport concern
- `hls` property (raw HLS.js instance) removed — backend-internal
- `setConfig(opts)` removed — use typed methods (`auth()`, `baseUrl()`, `volume()`)

**Event renames**

- `item` event renamed to `current`
- `playlist` event renamed to `queue`
- `playlistComplete` and `complete` events renamed to `queue:exhausted`
- `subtitleChanged` renamed to `subtitle`
- `subtitles` (cue data) renamed to `subtitleCue`
- `levelsChanged` renamed to `level-switched`
- `speed` event removed — use `playbackRate` event

**Event payload shape changes**

- `play` / `pause`: `TimeData` → `ActionOptions`
- `time` / `seek` / `seeked` / `duration`: multi-field struct → `{ time: number }` or `{ duration: number }`
- `current` (was `item`): `PlaylistItem` → `{ item: T; index: number }`
- `error` / `warning`: `MediaError` / `string` → `PlayerErrorEvent`
- `volume` / `mute`: `VolumeState` object → `{ level: number }` / `{ muted: boolean }`
- `levels` / `level-switched`: raw arrays → `{ levels: QualityLevel[] }` / `{ level: number }`
- `audioTracks`: `AudioTrack[]` → `{ tracks: AudioTrack[] }`
- `subtitle` (was `subtitleChanged`): `SubtitleTrack | undefined` → `{ index: number | null }`
- `fullscreen` / `pip` / `theater` / `float`: `boolean` → `{ active: boolean }`
- `waiting` / `canplay`: `HTMLVideoElement` → `void`

**PlaylistItem field changes**

- `file: string` renamed to `url?: string` — **silent break if server emits `file` and app passes items directly to player**
- `image: string` renamed to `poster?: string`
- `duration: string` (formatted) changed to `duration?: number` (seconds) — **formatters will receive NaN**
- `description` / `year` / `uuid` / `seasonName` removed
- `progress: { time, date }` reshaped to `progress?: { timestamp, percentage }`
- `tracks[kind='skippers']` sidecar VTT format replaced by `skippers: { intro?, recap?, credits? }` structured object
- `tracks[kind='sprite']` replaced by `previewSpriteUrl?: string` top-level field

**Package / build**

- `dependencies: { "hls.js" }` removed; install `hls.js` as your own peer dependency
- `@nomercy-entertainment/media-session` removed — media session is now `plugins/media-session`
- `webvtt-parser` removed — kit cue parser registry handles VTT
- Raw `./src/*` export paths removed — all public API via `dist/` subpaths only
- CJS and UMD builds dropped — ESM only (`"type": "module"`)
- `main` field now points to `./dist/index.js` (ESM)

**Migration guide:** See [MIGRATION.md](./MIGRATION.md) for per-change detail, code examples, and downstream project notes.

---

## [1.2.7] — 2026-04-06

### Fixed
- Dependency vulnerabilities: bumped `brace-expansion`, `picomatch`, `path-to-regexp`, `yaml` (dependabot GHSA advisories)

---

## [1.2.6] — 2026-04-06

### Changed
- Rebuilt dist with `nomercy-media-session` 1.1.3

---

## [1.2.5] — 2026-04-06

### Fixed
- Rebuilt with `nomercy-media-session` 1.1.2 (ChapterInformation fallback)

---

## [1.2.4] — 2026-04-06

### Changed
- Bumped `nomercy-media-session` to `^1.1.2`

---

## [1.2.3] — 2026-04-03

### Added
- Media Session: chapter markers and MediaSession action handler cleanup integrated

---

## [1.2.2] — 2026-03-09

### Fixed
- Seven ship-blocking bugs: HLS error recovery, VTT parsing edge cases, PIP listener leaks, seek event payload shape

---

## [1.2.0] — 2026-03-08

### Added
- `toTitleCase` exported as standalone function
- Utility functions reference page added to wiki

### Removed
- `String.prototype` extensions (toTitleCase was previously monkey-patching the prototype)

---

## [1.1.0] — 2026-03-08

### Added
- `token` getter/setter for reactive access token resolution (consumers no longer need to pass a static string at setup time)

### Fixed
- `hasListeners()` now detects listeners registered with `once()`

---

## [1.0.3] — 2026-03-08

### Fixed
- Translations file fetch queued via `queueMicrotask` for correct async ordering

---

## [1.0.2] — 2026-03-08

### Fixed
- Locale files bundled into dist to avoid CORS failures when fetching from raw.githubusercontent.com

---

## [1.0.1] — 2026-03-08

### Fixed
- `createSubtitleOverlay()` now creates the `subtitleSafeZone` element
- Test coverage expanded from 491 to 631 tests

---

## [1.0.0] — 2026-03-07

Stable release. All beta.x API stabilised. Full backwards-compat shim layer for 0.x consumers retained.

### Changed
- Fonts externalized to CDN; `tailwind-merge` removed; `hls.js` externalized from ESM/CJS bundles

---

## [1.0.0-beta series] — 2026-02-28 → 2026-03-07

A series of 28 pre-releases that collectively introduced the following breaking changes relative to 0.6.x.
All breaking changes have **deprecated shims** on the 1.x prototype unless noted.

### Breaking Changes (0.6.x → 1.0.0)

**Architecture**
- Monolithic `index.ts` split into focused mixin modules (`playback`, `volume`, `display`, `subtitles`, `audio`, `quality`, `chapters`, `skippers`, `playlist`, `dom`, `translations`, `events`, `ui-state`, `core`). Public API surface unchanged; internal imports no longer valid.

**Constructor / factory**
- Package default export is now a factory function `nmplayer(id?) => NMPlayer`, not a class. `new NoMercyPlayer(opts)` no longer works. Shim: cast player already handles both shapes in VideoPlayer.vue.

**`ready` event timing** (breaking, no shim)
- `ready` now fires immediately after `init()` (player API available), not on `durationchange` (media loaded). Consumers who used `ready` to know that duration was valid must now wait for `duration` event or check `getDuration()` after a `time` event.

**Removed events** (no shim)
- `back-button` — removed. Consumer code listening to this event receives nothing.
- `absolutePositionReady` — removed.
- `overlay` — removed.
- `nextClick` — removed.
- `controls` / `showControls` / `hideControls` — **deprecated forwarders re-emit these** from the new `active` event, so existing listeners continue to work.

**Renamed events** (deprecated aliases kept)
- `captionsList` → `subtitleList`
- `captionsChanged` → `subtitleChanged`
- `captionsChanging` → `subtitleChanging`
- `dynamicControls` → `interaction`
- `displayClick` → `player-click`
- `display-message` → `message`
- `remove-message` → `message-dismiss`

**Added events**
- `complete` — fires when a single item finishes (before `playlistComplete`)
- `player-dblclick`

**Getter/setter API unification** (old names kept as deprecated shims)
- All `get*` / `set*` / `current*` prefixes removed from the public API. Methods are now dual getter/setters: e.g. `volume()` to read, `volume(50)` to write.
- Renamed: `rewindVideo(t)` → `rewind(t)`, `forwardVideo(t)` → `forward(t)`
- Caption → subtitle renaming: `getCaptionsList` → `subtitles()`, `setCurrentCaption` → `subtitle(index)`, etc.

**Index convention change for subtitle and quality** (breaking when using raw indices)
- Subtitle: `subtitle(-1)` = Off (was: `setCurrentCaption(0)` = Off; index 0 meant Off, 1+ real tracks)
- Quality: `quality(-1)` = Auto (was: Auto prepended at index 0)
- Deprecated shims `getCurrentQuality()` and `setCurrentQuality()` translate the old convention automatically.

**Methods without deprecated shims** (cast-player uses these)
- `getAudioTrack()` → use `audioTrackIndex()`
- `getSubtitleTrack()` → use `subtitleIndex()`
- `getSubtitleTracks()` → use `subtitles()`
- `setAudioTrack(id)` → use `audioTrack(index)`
- `setSubtitleTrack(id)` → use `subtitle(index)`
- `setQuality(id)` → use `quality(index)`
- `getActualQualityLabel()` → not exposed; no equivalent; quality label is in `qualityLevels()[index].label`
- `getAutoSkipChapters()` / `setAutoSkipChapters()` → not in video-player API at any version (was a UI-layer concept in old cast receiver)
- `getCurrentChapter()` with no args → shim exists but now requires `currentTime: number`; zero-arg call returns `undefined`

**`PlaylistItem` shape**
- Fields `subtitle`, `backdrop`, `resumeFromMs`, `overview` are not part of the typed `PlaylistItem` interface. These are application-level extensions that consumers add via the generic `T` parameter on `NMPlayer<T>`. The cast player's `playlistItem()` call relies on these fields being present — they will be if the playlist was loaded with items that include them, but TypeScript won't see them without the generic.

---

## [0.6.10] — baseline (cast-player pin)

Last 0.x release before the 1.0 rewrite. See git history for individual 0.x changes.
