{"id":47695327,"url":"https://github.com/noma4i/nitro-player","last_synced_at":"2026-06-11T07:00:56.759Z","repository":{"id":345311723,"uuid":"1184214979","full_name":"noma4i/nitro-player","owner":"noma4i","description":"Lightweight video player for React Native with HLS caching","archived":false,"fork":false,"pushed_at":"2026-04-01T12:00:00.000Z","size":1829,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-03T02:59:21.791Z","etag":null,"topics":["cache","hls","nitro","react-native","video-player"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noma4i.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-17T11:17:43.000Z","updated_at":"2026-04-01T12:00:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/noma4i/nitro-player","commit_stats":null,"previous_names":["noma4i/just_player","noma4i/nitro-player"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/noma4i/nitro-player","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noma4i%2Fnitro-player","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noma4i%2Fnitro-player/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noma4i%2Fnitro-player/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noma4i%2Fnitro-player/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noma4i","download_url":"https://codeload.github.com/noma4i/nitro-player/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noma4i%2Fnitro-player/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34186385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","hls","nitro","react-native","video-player"],"created_at":"2026-04-02T16:23:05.264Z","updated_at":"2026-06-11T07:00:56.751Z","avatar_url":"https://github.com/noma4i.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NitroPlay\n\nNative-first video player for React Native with shared HLS transport, stream cache, and first-frame preview utilities.\n\n## Installation\n\n```sh\nyarn add @noma4i/nitro-play react-native-nitro-modules\n```\n\n```sh\nnpm install @noma4i/nitro-play react-native-nitro-modules\n```\n\n```sh\npnpm add @noma4i/nitro-play react-native-nitro-modules\n```\n\n`react-native-nitro-modules \u003e= 0.35.0` is a required peer dependency. React and React Native come from your app.\n\n### iOS\n\n```sh\ncd ios \u0026\u0026 pod install\n```\n\n### Android\n\nNo extra steps - standard React Native 0.77+ autolinking handles the module.\n\n### Installing a specific tag from GitHub\n\nIf you need a tag or branch before it is published to npm:\n\n```sh\nyarn add @noma4i/nitro-play@github:noma4i/nitro-player#v1.1.0-beta.1\n```\n\n## Quick Start\n\n```tsx\nimport React from 'react';\nimport { NitroPlayerView } from '@noma4i/nitro-play';\n\nexport function FeedCard() {\n  return (\n    \u003cNitroPlayerView\n      source={{\n        uri: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8',\n        startup: 'lazy',\n        transport: { mode: 'auto' },\n        retention: {\n          preload: 'metadata',\n          offscreen: 'metadata',\n          feedPoolEligible: true\n        },\n        preview: { mode: 'listener', autoThumbnail: true }\n      }}\n      resizeMode=\"contain\"\n      keepScreenAwake\n      style={{ width: '100%', aspectRatio: 16 / 9 }}\n    /\u003e\n  );\n}\n```\n\n## Core API\n\n| Surface                             | Purpose                                                            |\n| ----------------------------------- | ------------------------------------------------------------------ |\n| `NitroPlayerView`                   | Declarative native view with fullscreen, attach, and event props   |\n| `NitroPlayer`                       | Imperative player with properties, methods, and `addEventListener` |\n| `createNitroSource(config)`         | URI normalization and hybrid source factory                        |\n| `streamCache`                       | Prefetch, header-aware stats, cache clear                          |\n| `videoPreview`                      | Generated and cache-only first-frame lookup                        |\n| `usePlaybackState(player)`          | Subscribes to `onPlaybackState` and returns latest snapshot        |\n| `useEvent(player, event, listener)` | Managed event subscription helper                                  |\n\nDeep-dive reference lives in [docs/player-api.md](docs/player-api.md).\n\n## Source DSL\n\nTop-level `NitroSourceConfig` fields:\n\n| Field       | Type                         | Purpose                                                                           |\n| ----------- | ---------------------------- | --------------------------------------------------------------------------------- |\n| `uri`       | `string \\| number`           | Network URL, local `file://` URI, absolute local file path, or React Native asset |\n| `headers`   | `Record\u003cstring, string\u003e`     | Request headers (part of cache/preview identity)                                  |\n| `metadata`  | `NitroSourceMetadata`        | Player-facing media metadata                                                      |\n| `startup`   | `'eager' \\| 'lazy'`          | Startup strategy (default `eager`)                                                |\n| `buffer`    | `BufferConfig`               | Explicit buffering policy                                                         |\n| `retention` | `NitroSourceRetentionConfig` | Preload, offscreen retention, trim, feed eligibility                              |\n| `transport` | `NitroSourceTransportConfig` | Transport routing policy                                                          |\n| `preview`   | `NitroSourcePreviewConfig`   | First-frame generation policy                                                     |\n\n### `NitroSourceMetadata`\n\nAll fields optional.\n\n| Field         | Type     |\n| ------------- | -------- |\n| `title`       | `string` |\n| `subtitle`    | `string` |\n| `description` | `string` |\n| `artist`      | `string` |\n| `imageUri`    | `string` |\n\n### `NitroSourceRetentionConfig`\n\n| Field              | Type                                 | Purpose                             |\n| ------------------ | ------------------------------------ | ----------------------------------- |\n| `preload`          | `'none' \\| 'metadata' \\| 'buffered'` | Initial preload depth               |\n| `offscreen`        | `'cold' \\| 'metadata' \\| 'hot'`      | Offscreen retention level           |\n| `trimDelayMs`      | `number`                             | Delayed trim window                 |\n| `feedPoolEligible` | `boolean`                            | Participate in native feed hot pool |\n\n### `NitroSourceTransportConfig`\n\n| Field  | Type                            | Purpose                            |\n| ------ | ------------------------------- | ---------------------------------- |\n| `mode` | `'auto' \\| 'direct' \\| 'proxy'` | Transport routing (default `auto`) |\n\n`auto` prefers the shared HLS proxy and falls back to direct when the proxy cannot be readied for the active source generation. `direct` skips proxy routing. `proxy` forces proxy routing.\n\n### `NitroSourcePreviewConfig`\n\n| Field           | Type                                 | Default    | Purpose                                              |\n| --------------- | ------------------------------------ | ---------- | ---------------------------------------------------- |\n| `mode`          | `'listener' \\| 'always' \\| 'manual'` | `listener` | Automatic first-frame behavior                       |\n| `autoThumbnail` | `boolean`                            | `true`     | Native auto-thumbnail placeholder for attached views |\n| `maxWidth`      | `number`                             | `480`      | Output width hint                                    |\n| `maxHeight`     | `number`                             | `480`      | Output height hint                                   |\n| `quality`       | `number`                             | `70`       | JPEG quality hint                                    |\n\nSee [docs/source-config.md](docs/source-config.md) for source profiles and identity rules, and [docs/buffer-config.md](docs/buffer-config.md) for `BufferConfig` (Android `minBufferMs`, `maxBufferMs`, iOS `preferredForwardBufferDurationMs`, `preferredPeakBitRate`, shared `livePlayback`).\n\n## `NitroPlayerView`\n\n| Prop                  | Type                            | Notes                                 |\n| --------------------- | ------------------------------- | ------------------------------------- |\n| `source`              | `NitroSourceConfig`             | Required                              |\n| `playerDefaults`      | `NitroPlayerDefaults`           | Declarative startup state             |\n| `controls`            | `boolean`                       | Native controls                       |\n| `resizeMode`          | `ResizeMode`                    | `contain \\| cover \\| stretch \\| none` |\n| `keepScreenAwake`     | `boolean`                       | Screen wake lock                      |\n| `surfaceType`         | `'surface' \\| 'texture'`        | Android only                          |\n| `style`               | `ViewStyle`                     | Inherited from `ViewProps`            |\n| `onAttached`          | `(player: NitroPlayer) =\u003e void` | View attached                         |\n| `onDetached`          | `() =\u003e void`                    | View detached                         |\n| `onFullscreenChange`  | `(value: boolean) =\u003e void`      | Fullscreen state changed              |\n| `willEnterFullscreen` | `() =\u003e void`                    | Pre-enter hook                        |\n| `willExitFullscreen`  | `() =\u003e void`                    | Pre-exit hook                         |\n\n### `NitroPlayerViewRef`\n\n| Member                              | Type                   | Notes                              |\n| ----------------------------------- | ---------------------- | ---------------------------------- |\n| `player`                            | `NitroPlayer`          | Imperative player backing the view |\n| `isAttached`                        | `boolean`              | Native attach state                |\n| `enterFullscreen()`                 | `void`                 | Enter fullscreen                   |\n| `exitFullscreen()`                  | `void`                 | Exit fullscreen                    |\n| `addEventListener(event, listener)` | `ListenerSubscription` | View events only                   |\n\n## `NitroPlayer`\n\n### Properties\n\n| Property                 | Type                     | Get | Set |\n| ------------------------ | ------------------------ | --- | --- |\n| `source`                 | `NitroPlayerSource`      | yes | no  |\n| `status`                 | `NitroPlayerStatus`      | yes | no  |\n| `playbackState`          | `PlaybackState`          | yes | no  |\n| `memorySnapshot`         | `MemorySnapshot`         | yes | no  |\n| `duration`               | `number`                 | yes | no  |\n| `currentTime`            | `number`                 | yes | yes |\n| `volume`                 | `number`                 | yes | yes |\n| `muted`                  | `boolean`                | yes | yes |\n| `loop`                   | `boolean`                | yes | yes |\n| `rate`                   | `number`                 | yes | yes |\n| `isPlaying`              | `boolean`                | yes | no  |\n| `isBuffering`            | `boolean`                | yes | no  |\n| `isVisualReady`          | `boolean`                | yes | no  |\n| `bufferDuration`         | `number`                 | yes | no  |\n| `bufferedPosition`       | `number`                 | yes | no  |\n| `mixAudioMode`           | `MixAudioMode`           | yes | yes |\n| `ignoreSilentSwitchMode` | `IgnoreSilentSwitchMode` | yes | yes |\n| `playInBackground`       | `boolean`                | yes | yes |\n| `playWhenInactive`       | `boolean`                | yes | yes |\n\n### Methods\n\n| Method                              | Returns                | Notes                                 |\n| ----------------------------------- | ---------------------- | ------------------------------------- |\n| `play()`                            | `void`                 | Start playback; valid before `onLoad` |\n| `pause()`                           | `void`                 | Pause playback                        |\n| `seekTo(seconds)`                   | `void`                 | Absolute seek                         |\n| `seekBy(seconds)`                   | `void`                 | Relative seek                         |\n| `initialize()`                      | `Promise\u003cvoid\u003e`        | Manual initialization                 |\n| `preload()`                         | `Promise\u003cvoid\u003e`        | Warm source without starting playback |\n| `replaceSourceAsync(source)`        | `Promise\u003cvoid\u003e`        | Swap active source                    |\n| `clearSourceAsync()`                | `Promise\u003cvoid\u003e`        | Clear source and keep player reusable |\n| `release()`                         | `void`                 | Terminal teardown                     |\n| `addEventListener(event, listener)` | `ListenerSubscription` | Subscribe to a player event           |\n\n## `PlaybackState`\n\n| Field               | Type                    | Notes                                                                 |\n| ------------------- | ----------------------- | --------------------------------------------------------------------- |\n| `status`            | `NitroPlayerStatus`     | `idle \\| loading \\| buffering \\| playing \\| paused \\| ended \\| error` |\n| `currentTime`       | `number`                | Seconds                                                               |\n| `duration`          | `number`                | Seconds                                                               |\n| `bufferDuration`    | `number`                | Seconds buffered ahead                                                |\n| `bufferedPosition`  | `number`                | Absolute buffered position                                            |\n| `rate`              | `number`                | Effective playback rate                                               |\n| `isPlaying`         | `boolean`               | Native playing state                                                  |\n| `isBuffering`       | `boolean`               | Native buffering state                                                |\n| `isVisualReady`     | `boolean`               | First visual frame readiness                                          |\n| `error`             | `PlaybackError \\| null` | Present when `status === 'error'`                                     |\n| `nativeTimestampMs` | `number`                | Native event timestamp                                                |\n\n## Player Events\n\n| Event               | Payload              |\n| ------------------- | -------------------- |\n| `onPlaybackState`   | `PlaybackState`      |\n| `onLoadStart`       | `onLoadStartData`    |\n| `onLoad`            | `onLoadData`         |\n| `onError`           | `PlaybackError`      |\n| `onFirstFrame`      | `onFirstFrameData`   |\n| `onBandwidthUpdate` | `BandwidthData`      |\n| `onVolumeChange`    | `onVolumeChangeData` |\n\nSubscribe directly on the player instance:\n\n```ts\nconst sub = player.addEventListener('onPlaybackState', state =\u003e {\n  console.log(state.status, state.currentTime);\n});\n// later\nsub.remove();\n```\n\n## Stream Cache \u0026 Preview\n\n| API                                   | Returns                           | Purpose                                |\n| ------------------------------------- | --------------------------------- | -------------------------------------- |\n| `streamCache.prefetch(source)`        | `Promise\u003cvoid\u003e`                   | Warm transport and first segment cache |\n| `streamCache.getStats()`              | `Promise\u003cStreamCacheStats\u003e`       | Total cache stats                      |\n| `streamCache.getStats(source)`        | `Promise\u003cStreamSourceCacheStats\u003e` | Per-source stats, header-aware         |\n| `streamCache.clear()`                 | `Promise\u003cboolean\u003e`                | Clear stream disk cache                |\n| `videoPreview.getFirstFrame(source)`  | `Promise\u003cstring \\| null\u003e`         | Cached or generated first-frame path   |\n| `videoPreview.peekFirstFrame(source)` | `Promise\u003cstring \\| null\u003e`         | Cached-only lookup (no generation)     |\n| `videoPreview.clear()`                | `Promise\u003cboolean\u003e`                | Clear preview artifacts                |\n\nAll source-taking methods accept either a URL string or `{ uri, headers }`. Use the object form whenever headers are part of request identity. See [docs/stream-runtime.md](docs/stream-runtime.md).\n\n## Hooks\n\n| Hook               | Signature                                                                 | Purpose                                                     |\n| ------------------ | ------------------------------------------------------------------------- | ----------------------------------------------------------- |\n| `usePlaybackState` | `(player: NitroPlayer \\| null \\| undefined) =\u003e PlaybackState \\| null`     | Subscribes to `onPlaybackState` and returns latest snapshot |\n| `useEvent`         | `\u003cT extends keyof AllNitroPlayerEvents\u003e(player, event, callback) =\u003e void` | Managed event subscription with automatic cleanup           |\n\n## `createNitroSource`\n\n```ts\nimport { createNitroSource } from '@noma4i/nitro-play';\n\nconst source = createNitroSource({ uri: require('./intro.mp4') });\n```\n\nResolves React Native asset references via `Image.resolveAssetSource`, validates the URI, and returns a hybrid `NitroPlayerSource`. Usually you do not need to call this manually: `NitroPlayerView` and `NitroPlayer` accept a plain `NitroSourceConfig` and normalize it internally. Call `createNitroSource` explicitly when you want to reuse the same native source object across multiple `replaceSourceAsync` calls.\n\n## Common Types\n\n| Type                     | Values / Shape                                                                                                    | Purpose                                                           |\n| ------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |\n| `NitroPlayerStatus`      | `idle \\| loading \\| buffering \\| playing \\| paused \\| ended \\| error`                                             | Player status                                                     |\n| `ResizeMode`             | `contain \\| cover \\| stretch \\| none`                                                                             | View scaling                                                      |\n| `MixAudioMode`           | `mixWithOthers \\| doNotMix \\| duckOthers \\| auto`                                                                 | Audio session mix behavior                                        |\n| `IgnoreSilentSwitchMode` | `auto \\| ignore \\| obey`                                                                                          | iOS silent switch handling                                        |\n| `PreloadLevel`           | `none \\| metadata \\| buffered`                                                                                    | Initial preload depth                                             |\n| `OffscreenRetention`     | `cold \\| metadata \\| hot`                                                                                         | Offscreen retention level                                         |\n| `MemoryRetentionState`   | `cold \\| metadata \\| hot`                                                                                         | Retention snapshot value                                          |\n| `MemorySnapshot`         | `{ playerBytes, sourceBytes, totalBytes, preloadLevel, retentionState, isAttachedToView, isPlaying }`             | Current player memory footprint                                   |\n| `NitroPlayerDefaults`    | `{ loop?, muted?, volume?, rate?, mixAudioMode?, ignoreSilentSwitchMode?, playInBackground?, playWhenInactive? }` | Declarative startup state for `NitroPlayerView.playerDefaults`    |\n| `NitroPlayerError`       | Discriminated on `LibraryError \\| PlayerError \\| SourceError \\| NitroPlayerViewError \\| UnknownError`             | Typed error hierarchy used by `onError` and thrown runtime errors |\n\n## Runtime Contract\n\n| Area                    | Behavior                                                                                                                                                                  |\n| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Early play              | `play()` before `onLoad` is canonical                                                                                                                                     |\n| Playback state          | Built from native readiness, buffering, and actual playing state                                                                                                          |\n| HLS startup             | Native runtime uses lazy startup and bounded startup recovery                                                                                                             |\n| Proxy fallback          | `transport.mode='auto'` may fall back to direct URL if proxy is unavailable                                                                                               |\n| First frame             | `onFirstFrame` is sticky per active source generation                                                                                                                     |\n| Preview policy          | `preview.mode='listener'` auto-captures for attached views when `autoThumbnail !== false`; `always` warms preview automatically; `manual` disables background auto-warmup |\n| Mounted-view reveal     | Attached `NitroPlayerView` owns native auto-thumbnail/first-frame placeholder by default; app code should not require JS poster swapping for active playback surfaces     |\n| Manual preview          | `videoPreview.getFirstFrame(source)` returns cached/generated frame path                                                                                                  |\n| Cached preview reuse    | `videoPreview.peekFirstFrame(source)` returns only an existing cached frame path and never starts generation                                                              |\n| Stream/preview identity | `{ uri, headers }` is the canonical identity for cache stats and preview artifacts                                                                                        |\n| Stream cache            | `streamCache.prefetch(source)` is safe to call repeatedly                                                                                                                 |\n\nAbsolute local file paths are accepted on both iOS and Android and are normalized internally to `file://` URLs. App code should prefer canonical `file://` URIs when it owns freshly recorded media paths.\n\n## Example App\n\nThe local [example](example/README.md) is a runtime lab, not just a smoke test.\n\nIt covers:\n\n- hero playback switching between `transport.mode='auto'`, header-isolated HLS, and direct MP4\n- `streamCache.prefetch/getStats/clear` and `videoPreview.getFirstFrame/peekFirstFrame/clear`\n- a three-player feed stress block with the same HLS URL under different headers\n- live observation of `onLoad`, `onError`, `onFirstFrame`, bandwidth, attach state, and `isVisualReady`\n\n## Documentation\n\n| File                                                   | Purpose                                                 |\n| ------------------------------------------------------ | ------------------------------------------------------- |\n| [docs/player-api.md](docs/player-api.md)               | Public player, view, events, hooks                      |\n| [docs/source-config.md](docs/source-config.md)         | Source DSL and normalized source model                  |\n| [docs/buffer-config.md](docs/buffer-config.md)         | `buffer` tuning                                         |\n| [docs/lifecycle-guide.md](docs/lifecycle-guide.md)     | `retention` model and startup intent                    |\n| [docs/memory-management.md](docs/memory-management.md) | Retention states, snapshots, feed pool                  |\n| [docs/stream-runtime.md](docs/stream-runtime.md)       | Shared transport runtime, `streamCache`, `videoPreview` |\n| [docs/migration-1.0.md](docs/migration-1.0.md)         | Migration to the current beta DSL                       |\n\n## Requirements\n\n| Dependency                 | Version     |\n| -------------------------- | ----------- |\n| React Native               | `\u003e= 0.77.0` |\n| react-native-nitro-modules | `\u003e= 0.35.0` |\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoma4i%2Fnitro-player","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoma4i%2Fnitro-player","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoma4i%2Fnitro-player/lists"}