{"_id":"@code_with_sachin/ngx-video-js","name":"@code_with_sachin/ngx-video-js","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.1":{"name":"@code_with_sachin/ngx-video-js","version":"0.1.1","description":"Angular wrapper for the Video.js 10 web components — the full default video skin in one standalone component, SSR-safe and lazily loaded.","keywords":["angular","videojs","video.js","video","player","media","hls","custom-elements","web-components","ssr","standalone","signals"],"homepage":"https://sachinsingh.me/packages/video","license":"MIT","author":{"name":"Sachin Singh","url":"https://sachinsingh.me"},"repository":{"type":"git","url":"git+https://github.com/CodeWithSachin/kshatriya-portfolio.git","directory":"projects/ngx-video-js"},"bugs":{"url":"https://github.com/CodeWithSachin/kshatriya-portfolio/issues"},"publishConfig":{"access":"public"},"peerDependencies":{"@angular/common":"^22.0.0","@angular/core":"^22.0.0","@videojs/html":"^10.0.0-beta.27"},"dependencies":{"tslib":"^2.3.0"},"sideEffects":false,"module":"fesm2022/code_with_sachin-ngx-video-js.mjs","typings":"types/code_with_sachin-ngx-video-js.d.ts","exports":{"./package.json":{"default":"./package.json"},".":{"types":"./types/code_with_sachin-ngx-video-js.d.ts","default":"./fesm2022/code_with_sachin-ngx-video-js.mjs"}},"type":"module","gitHead":"90c2b7b5c9e81107ef27ca4f7e7d47997629b1f1","_id":"@code_with_sachin/ngx-video-js@0.1.1","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-PV2y86O0LIbYbKpn/2rEoO0+PK6voOEqfQCqscys/SzInT+exl/PKo/KRnQZbA8RQDlBkIfJTxqu9KHOjfC/vw==","shasum":"a564c00b275fb1b9af0c37c57356796a460e091d","tarball":"https://registry.npmjs.org/@code_with_sachin/ngx-video-js/-/ngx-video-js-0.1.1.tgz","fileCount":5,"unpackedSize":282572,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCB0iQaUncn+CD4oZcv9lgFQVPtQbTkjXS92q3clnMbOAIgXZTunmUbMS4BxYgTf2gLbZcR1tfKRhSO7I1XGjZv1PI="}]},"_npmUser":{"name":"code_with_sachin","email":"sachinnnsingh0510@gmail.com"},"directories":{},"maintainers":[{"name":"code_with_sachin","email":"sachinnnsingh0510@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ngx-video-js_0.1.1_1788637001849_0.7747463288414875"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T19:36:41.670Z","0.1.1":"2026-09-05T19:36:42.010Z","modified":"2026-09-05T19:36:42.308Z"},"maintainers":[{"name":"code_with_sachin","email":"sachinnnsingh0510@gmail.com"}],"description":"Angular wrapper for the Video.js 10 web components — the full default video skin in one standalone component, SSR-safe and lazily loaded.","homepage":"https://sachinsingh.me/packages/video","keywords":["angular","videojs","video.js","video","player","media","hls","custom-elements","web-components","ssr","standalone","signals"],"repository":{"type":"git","url":"git+https://github.com/CodeWithSachin/kshatriya-portfolio.git","directory":"projects/ngx-video-js"},"author":{"name":"Sachin Singh","url":"https://sachinsingh.me"},"bugs":{"url":"https://github.com/CodeWithSachin/kshatriya-portfolio/issues"},"license":"MIT","readme":"# @code_with_sachin/ngx-video-js\n\nAngular wrapper for the [Video.js 10](https://videojs.org) web components — the full\ndefault video skin in one standalone component, SSR-safe and lazily loaded.\n\n**[Live demo & docs → https://sachinsingh.me/packages/video](https://sachinsingh.me/packages/video)**\n\nVideo.js 10 ships its player as custom elements: `<video-player>` holds the state,\n`<video-skin>` is the packaged UI, and a plain `<video>` handles the media. Those\nelements are framework-agnostic and already do everything — controls, menus,\nhotkeys, gestures, indicators, thumbnails, Cast/AirPlay/PiP. This package is the\nthin Angular layer over them: inputs instead of attributes, no custom element\nregistration on the server, and no stylesheet to remember to import.\n\n- SSR-safe by construction — the element definitions load inside `afterNextRender`.\n- Skin CSS travels inside the skin's shadow root, so nothing leaks into your app.\n- Definitions are registered once per document, no matter how many players render.\n- Standalone component, signal inputs, zoneless-friendly.\n\n## Install\n\n```bash\nnpm i @code_with_sachin/ngx-video-js @videojs/html\n```\n\nPeer dependencies: Angular 22+, `@videojs/html` 10 (currently beta).\n\n## Usage\n\n```ts\nimport { Component } from '@angular/core';\nimport { NgxVideoPlayer } from '@code_with_sachin/ngx-video-js';\n\n@Component({\n  selector: 'app-talk',\n  imports: [NgxVideoPlayer],\n  template: `\n    <ngx-video-player\n      src=\"https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4\"\n      poster=\"https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.webp\"\n      posterAlt=\"Cyclist riding past a warehouse\"\n    />\n  `,\n  styles: `\n    ngx-video-player {\n      max-width: 60rem;\n    }\n  `,\n})\nexport class Talk {}\n```\n\n## Inputs\n\n| Input         | Type                                     | Default      | Notes                                                                                                 |\n| ------------- | ---------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------- |\n| `src`         | `string`                                 | **required** | Passed to the inner `<video>`. MP4, or HLS where the browser plays it natively.                       |\n| `poster`      | `string`                                 | `''`         | Rendered as an `<img>` in the skin's poster slot — `srcset` and friends work because you own the tag. |\n| `posterAlt`   | `string`                                 | `''`         | Leave empty when the poster is decorative.                                                            |\n| `placeholder` | `string`                                 | `''`         | Tiny image blurred behind the poster while it loads (`placeholdersrc`).                               |\n| `preload`     | `'none' \\| 'metadata' \\| 'auto'`         | `'metadata'` |                                                                                                       |\n| `crossOrigin` | `'' \\| 'anonymous' \\| 'use-credentials'` | `''`         | Needed for cross-origin text tracks.                                                                  |\n| `autoplay`    | `boolean`                                | `false`      | Browsers require `muted` alongside it.                                                                |\n| `muted`       | `boolean`                                | `false`      |                                                                                                       |\n| `loop`        | `boolean`                                | `false`      |                                                                                                       |\n\n`playsinline` is always set — without it iOS Safari takes the video fullscreen on play.\n\n## Language\n\n51 locale packs ship with `@videojs/html`, and only the one in use is fetched.\nEvery label, tooltip, menu entry and screen-reader announcement follows.\n\n```html\n<ngx-video-player lang=\"hi\" src=\"/media/talk.mp4\" />\n\n<!-- Leave it empty and the player follows the nearest lang attribute, so a\n     site that already sets <html lang> needs nothing here. -->\n<ngx-video-player src=\"/media/talk.mp4\" />\n```\n\n## Live\n\nThe live controls are a **separate packaged skin** — `<live-video-player>` and\n`<live-video-skin>` — not a mode of the on-demand one, whose template has no\nlive controls at all. `skin=\"live\"` swaps the pair and keeps the media, the\ntracks and anything projected into it.\n\n```html\n<!-- A LIVE pill that turns red at the edge where the duration would be,\n     and a DVR window on the time slider. -->\n<ngx-video-player skin=\"live\" src=\"https://cdn.example.com/live.m3u8\" />\n```\n\nAn endless manifest already reports itself as live, so `streamType` is only for\nmedia that cannot say so — a proxy reporting a finite duration, a recording\nplayed as live.\n\n## Removing controls\n\n### With the custom skin\n\n`skin=\"custom\"` renders the default video skin ejected into this package's own\nAngular template, in light DOM, with every part behind an `@if`. A part you\nswitch off is not in the document — there is no CSS rule for a viewer to\noverride in devtools and no hidden button to re-enable.\n\n```ts\nimport { NgxVideoPlayer, type VideoControls } from '@code_with_sachin/ngx-video-js';\n\n@Component({\n  imports: [NgxVideoPlayer],\n  template: `<ngx-video-player skin=\"custom\" [controls]=\"controls\" src=\"/media/module-3.mp4\" />`,\n})\nexport class Lesson {\n  readonly controls: VideoControls = {\n    progressBar: false,\n    settingsMenu: false,\n    pipButton: false,\n    fullscreenButton: false,\n  };\n}\n```\n\n**Removing a control removes its shortcuts too.** Drop `progressBar` and the\narrow keys, `j`/`l`, `Home`/`End`, `0`–`9`, the double-tap seek and the seek\nindicator all go with it — otherwise the restriction would be decoration. The\nsame applies to submenus: closing `settingsMenu` takes `<`/`>` with it, because\na shortcut whose control is unreachable is a way back in.\n\n| Group     | Keys                                                                                                                                                      |\n| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Layers    | `poster`, `bufferingIndicator`, `errorDialog`, `overlay`                                                                                                  |\n| Primary   | `controlBar`, `playButton`, `muteButton`, `volumeSlider`, `currentTime`, `progressBar`, `chapters`, `thumbnailPreview`, `remainingTime`, `captionsButton` |\n| Menu      | `settingsMenu`, `qualityMenu`, `audioMenu`, `speedMenu`, `captionsMenu`                                                                                   |\n| Secondary | `castButton`, `airplayButton`, `pipButton`, `fullscreenButton`                                                                                            |\n| Input     | `hotkeys`, `gestures`, `tooltips`, `statusAnnouncer`, `volumeIndicator`, `statusIndicator`, `seekIndicator`                                               |\n\nEvery key defaults to `true`. Containers collapse on their own: turn off all\nfour buttons in the second row and the row goes; turn off every submenu and the\nsettings trigger goes with them. Changes apply live — it is an Angular template,\nnot a custom element, so nothing remounts.\n\nTwo costs, both worth knowing before you switch: the skin's stylesheet travels\nwith the component rather than inside a shadow root, which is about 25 kB\ngzipped in whichever chunk imports the player, and you no longer pick up\nupstream design updates by bumping the version.\n\n### With the packaged skins\n\nThey have no per-button switches, and mostly do not need them: each control asks\nthe media whether it can do the thing and hides itself when the answer is no.\nThat is why the quality and audio menus are absent on a progressive MP4, and why\nAirPlay never appears outside Safari. The lever you hold is telling the media it\ncannot.\n\n```html\n<ngx-video-player disableRemotePlayback src=\"/media/talk.mp4\" />\n```\n\n`disableRemotePlayback` genuinely removes AirPlay and Cast — the availability\nwatch rejects and both buttons go `unsupported`. `disablePictureInPicture` does\n**not** currently do the same for PiP: 10.0.0-beta.27 derives that button's\navailability from the browser's capability alone, so the attribute stops the\nbrowser's own auto-PiP but leaves the skin's button in place, throwing if\npressed. Both are read when the media element attaches, so set them before the\nplayer mounts.\n\n## Anything else\n\nThe skin's own hotkeys and gestures are fixed — space, `k`, `m`, `f`, `c`, `i`,\n`j`/`l`, arrows, `0`–`9`, `<`/`>`, plus tap and double-tap. What you can do is\nadd more, and add the sibling components Video.js expects, by projecting them as\nchildren. They are not inputs here because there is nothing to wrap: they are\nelements, and the player context already reaches them.\n\n```html\n<ngx-video-player src=\"/media/talk.mp4\">\n  <!-- import '@videojs/html/ui/hotkey' -->\n  <media-hotkey keys=\"n\" action=\"seekStep\" value=\"30\"></media-hotkey>\n  <media-gesture type=\"doubletap\" action=\"toggleMuted\" region=\"center\"></media-gesture>\n\n  <!-- import '@videojs/html/media/google-cast' and '.../mux-data' -->\n  <google-cast></google-cast>\n  <mux-data env-key=\"...\"></mux-data>\n</ngx-video-player>\n```\n\nBindings are additive — the skin's own are still there, so a second binding on a\nkey it already uses fires twice. Actions a hotkey or gesture can fire:\n`togglePaused`, `toggleMuted`, `toggleFullscreen`, `toggleSubtitles`,\n`togglePictureInPicture`, `toggleControls` (gesture only), `seekStep`,\n`volumeStep`, `speedUp`, `speedDown`, and `seekToPercent` (hotkey only).\n\n## Sizing\n\n`<video-player>` is `display: contents` and `<video-skin>` is a full-width grid, so\nthe player takes its size from this component's host element:\n\n```css\nngx-video-player {\n  max-width: 60rem;\n  aspect-ratio: 16 / 9;\n}\n```\n\n## Theming\n\nThe skin reads CSS custom properties from the host document, so set them on the host:\n\n```css\nngx-video-player {\n  --media-accent-color: oklch(0.72 0.19 25); /* slider fill, focus, hover */\n  --media-accent-text-color: oklch(1 0 0); /* label colour on those states */\n  --media-border-radius: 0.75rem;\n  --media-scale-unit: 18px; /* scales the whole control layer */\n  --media-object-fit: cover;\n  --media-object-position: top;\n  --media-poster-placeholder-blur: 24px;\n}\n```\n\n## Requirements\n\n| Package         | Peer range        | Tested         | Notes                                                 |\n| --------------- | ----------------- | -------------- | ----------------------------------------------------- |\n| `@angular/core` | `^22.0.0`         | 22.0.5         | Standalone APIs, signal inputs and `afterNextRender`. |\n| `typescript`    | `~6.0.0`          | 6.0.3          | Whatever your Angular version supports.               |\n| `node`          | `>=20`            | 24.15.0        | Build and SSR only.                                   |\n| `@videojs/html` | `^10.0.0-beta.27` | 10.0.0-beta.27 | The custom elements. Still beta — pin it.             |\n\n## SSR\n\nThe component renders `<video-player><video-skin><video>` markup on the server and\nnever touches `customElements`, so prerendering works. The elements upgrade on\nhydration; until then the browser shows the poster `<img>`.\n\nThe media element is chosen from the URL, so the prerendered HTML already has the\nright tag — nothing swaps on hydration.\n\nLoading the definitions ahead of a player — during a route resolve, say — is a\nplain call. Name the provider to warm hls.js alongside the skin:\n\n```ts\nimport { loadVideoJsElements } from '@code_with_sachin/ngx-video-js';\n\nawait loadVideoJsElements('hls', 'live'); // memoised per module; no-op without customElements\n```\n\n## Going further\n\nThis wraps the _packaged_ skin. When you need to restructure the UI rather than\nrestyle it, eject the skin per the\n[Video.js guide](https://videojs.org/docs/framework/html/how-to/customize-skins) and\ncompose the `media-*` elements directly in your own Angular template — they are\ncustom elements, so `CUSTOM_ELEMENTS_SCHEMA` is all Angular needs.\n\n## Licence\n\nMIT © [Sachin Singh](https://sachinsingh.me)\n","readmeFilename":"README.md","_rev":"1-4c37f2bc872bb0b76c58eb2543cbe1b1"}