{"_id":"@bogdanrn/yt-embed","_rev":"5-f79e736a4da2e17312e0c72e64a4a62c","name":"@bogdanrn/yt-embed","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@bogdanrn/yt-embed","version":"0.1.0","author":{"name":"Bogdan Radu"},"license":"MIT","_id":"@bogdanrn/yt-embed@0.1.0","maintainers":[{"name":"bogdanrn","email":"bogdanrn@gmail.com"}],"homepage":"https://github.com/bogdanrn/yt-embed#readme","bugs":{"url":"https://github.com/bogdanrn/yt-embed/issues"},"dist":{"shasum":"8966fe7439045df6b2c95ffe0dd663dcd5d02575","tarball":"https://registry.npmjs.org/@bogdanrn/yt-embed/-/yt-embed-0.1.0.tgz","fileCount":11,"integrity":"sha512-8wnLSZwnp1Vx8Yill51SZQKngB6ciglv/NmB/as6hGiDnrASPF50LgnYwNCKVK8R1XiJrdoZgPCUbrRwLnunBg==","signatures":[{"sig":"MEUCIEuST0hi0FStmJ5KhrM3xOzq033rFsEGnrkA2jIQFUx+AiEA5xpNa4qVRosyHDB3UcJUQTpI0o80G43eoMQPhRh39aY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":109728},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"a68ff0f32053e616cd674c0082f4d540a55d3371","scripts":{"dev":"tsdown --watch","docs":"typedoc","lint":"biome check .","test":"vitest run","build":"tsdown","verify":"pnpm typecheck && pnpm lint && pnpm test && pnpm build","prepare":"simple-git-hooks","generate":"tsx scripts/generate.ts","lint:fix":"biome check --write .","prebuild":"pnpm generate","typecheck":"tsc --noEmit","test:watch":"vitest","postgenerate":"tsx scripts/inject-readme.ts","test:typecheck":"vitest --typecheck --run"},"_npmUser":{"name":"bogdanrn","email":"bogdanrn@gmail.com"},"repository":{"url":"git+https://github.com/bogdanrn/yt-embed.git","type":"git"},"_npmVersion":"11.12.1","description":"Promise-wrapped YouTube IFrame Player API","directories":{},"lint-staged":{"*.{js,jsx,ts,tsx,json,jsonc,md}":"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"},"sideEffects":false,"_nodeVersion":"24.15.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"packageManager":"pnpm@9.15.0","devDependencies":{"tsx":"^4.21.0","vite":"^8.0.10","jsdom":"^29.1.0","tsdown":"^0.21.10","vitest":"^4.1.5","typedoc":"^0.28.19","ts-morph":"^28.0.0","typescript":"^6.0.3","@types/node":"^25.6.0","lint-staged":"^16.4.0","@biomejs/biome":"^2.4.13","@types/youtube":"^0.2.0","@changesets/cli":"^2.31.0","@commitlint/cli":"^20.5.2","simple-git-hooks":"^2.13.1","@vitest/coverage-v8":"^4.1.5","typedoc-plugin-markdown":"^4.11.0","@commitlint/config-conventional":"^20.5.0"},"simple-git-hooks":{"commit-msg":"pnpm exec commitlint --edit $1","pre-commit":"pnpm exec lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/yt-embed_0.1.0_1777329303610_0.3396778014446158","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@bogdanrn/yt-embed","version":"0.1.1","author":{"name":"Bogdan Radu"},"license":"MIT","_id":"@bogdanrn/yt-embed@0.1.1","maintainers":[{"name":"bogdanrn","email":"bogdanrn@gmail.com"}],"homepage":"https://github.com/bogdanrn/yt-embed#readme","bugs":{"url":"https://github.com/bogdanrn/yt-embed/issues"},"dist":{"shasum":"14fc8e4105d441f82f6d9b3245fe63091d6a7fda","tarball":"https://registry.npmjs.org/@bogdanrn/yt-embed/-/yt-embed-0.1.1.tgz","fileCount":11,"integrity":"sha512-Cb1VJ7M5KBHp3E/SpRpR6BAdPVtQMUFxRceWNRDjEEQSsPVoDUeIzPjyrD1lTzpo4NVxA4FRIGATWkWgBnzZBg==","signatures":[{"sig":"MEQCIA8QjvdqHHXNgl0MoeMI0HVb4NeyfQj/BYDT0VQw7LMfAiBt6v5nCA43FUD0w4y2/HaUumi0Njv2+eCypH6AOB8RCg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bogdanrn%2fyt-embed@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":109657},"main":"./dist/index.cjs","type":"module","_from":"file:bogdanrn-yt-embed-0.1.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"tsdown --watch","docs":"typedoc","lint":"biome check .","test":"vitest run","build":"tsdown","verify":"pnpm typecheck && pnpm lint && pnpm test && pnpm build","generate":"tsx scripts/generate.ts","lint:fix":"biome check --write .","prebuild":"pnpm generate","typecheck":"tsc --noEmit","test:watch":"vitest","postgenerate":"tsx scripts/inject-readme.ts","test:typecheck":"vitest --typecheck --run"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:182b86cd-5ad7-4eef-baaa-0f828f478c70"}},"_resolved":"/tmp/3c9d2a04ce05701c04eb2cb19cff7508/bogdanrn-yt-embed-0.1.1.tgz","_integrity":"sha512-Cb1VJ7M5KBHp3E/SpRpR6BAdPVtQMUFxRceWNRDjEEQSsPVoDUeIzPjyrD1lTzpo4NVxA4FRIGATWkWgBnzZBg==","repository":{"url":"git+https://github.com/bogdanrn/yt-embed.git","type":"git"},"_npmVersion":"11.11.0","description":"Promise-wrapped YouTube IFrame Player API","directories":{},"lint-staged":{"*.{js,jsx,ts,tsx,json,jsonc,md}":"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"},"sideEffects":false,"_nodeVersion":"24.14.1","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.21.0","vite":"^8.0.10","jsdom":"^29.1.0","tsdown":"^0.21.10","vitest":"^4.1.5","typedoc":"^0.28.19","ts-morph":"^28.0.0","typescript":"^6.0.3","@types/node":"^25.6.0","lint-staged":"^16.4.0","@biomejs/biome":"^2.4.13","@types/youtube":"^0.2.0","@changesets/cli":"^2.31.0","@commitlint/cli":"^20.5.2","simple-git-hooks":"^2.13.1","@vitest/coverage-v8":"^4.1.5","typedoc-plugin-markdown":"^4.11.0","@commitlint/config-conventional":"^20.5.0"},"simple-git-hooks":{"commit-msg":"pnpm exec commitlint --edit $1","pre-commit":"pnpm exec lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/yt-embed_0.1.1_1777330262778_0.42079376065049456","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@bogdanrn/yt-embed","version":"0.1.2","author":{"name":"Bogdan Radu"},"license":"MIT","_id":"@bogdanrn/yt-embed@0.1.2","maintainers":[{"name":"bogdanrn","email":"bogdanrn@gmail.com"}],"homepage":"https://github.com/bogdanrn/yt-embed#readme","bugs":{"url":"https://github.com/bogdanrn/yt-embed/issues"},"dist":{"shasum":"3357c85b9a34d8068c3f8b1dc0fb2c43860f7dc5","tarball":"https://registry.npmjs.org/@bogdanrn/yt-embed/-/yt-embed-0.1.2.tgz","fileCount":11,"integrity":"sha512-19ayZgas6B892f0J9/G01zlh0hNIf9OH4GeN2bmTMdTOkwMWXsxn9t7zkv9deCX3SjeIPq/CoMPSE1FaM6mf8w==","signatures":[{"sig":"MEQCIBqZp/e8EEia2EL5VUbxmjRdkxXD3j7aYGgzA+HuNOzFAiAPGqvFWjOowG9N8WkHIfWD9W9zInbiJw/nXyY4mhyctw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bogdanrn%2fyt-embed@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":105329},"main":"./dist/index.cjs","type":"module","_from":"file:bogdanrn-yt-embed-0.1.2.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"tsdown --watch","docs":"typedoc","lint":"biome check .","test":"vitest run","build":"tsdown","verify":"pnpm typecheck && pnpm lint && pnpm test && pnpm build","generate":"tsx scripts/generate.ts","lint:fix":"biome check --write .","prebuild":"pnpm generate","typecheck":"tsc --noEmit","test:watch":"vitest","postgenerate":"tsx scripts/inject-readme.ts","test:typecheck":"vitest --typecheck --run"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:182b86cd-5ad7-4eef-baaa-0f828f478c70"}},"_resolved":"/tmp/223bc7e936399c818aba86f58868d89a/bogdanrn-yt-embed-0.1.2.tgz","_integrity":"sha512-19ayZgas6B892f0J9/G01zlh0hNIf9OH4GeN2bmTMdTOkwMWXsxn9t7zkv9deCX3SjeIPq/CoMPSE1FaM6mf8w==","repository":{"url":"git+https://github.com/bogdanrn/yt-embed.git","type":"git"},"_npmVersion":"11.12.1","description":"Promise-wrapped YouTube IFrame Player API","directories":{},"lint-staged":{"*.{js,jsx,ts,tsx,json,jsonc,md}":"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"},"sideEffects":false,"_nodeVersion":"24.15.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.21.0","vite":"^8.0.10","jsdom":"^29.1.0","tsdown":"^0.21.10","vitest":"^4.1.5","typedoc":"^0.28.19","ts-morph":"^28.0.0","typescript":"^6.0.3","@types/node":"^25.6.0","lint-staged":"^16.4.0","@biomejs/biome":"^2.4.13","@types/youtube":"^0.2.0","@changesets/cli":"^2.31.0","@commitlint/cli":"^20.5.2","simple-git-hooks":"^2.13.1","@vitest/coverage-v8":"^4.1.5","@commitlint/config-conventional":"^20.5.0"},"simple-git-hooks":{"commit-msg":"pnpm exec commitlint --edit $1","pre-commit":"pnpm exec lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/yt-embed_0.1.2_1777331803277_0.31794918695872565","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@bogdanrn/yt-embed","version":"0.2.0","author":{"name":"Bogdan Radu"},"license":"MIT","_id":"@bogdanrn/yt-embed@0.2.0","maintainers":[{"name":"bogdanrn","email":"bogdanrn@gmail.com"}],"homepage":"https://github.com/bogdanrn/yt-embed#readme","bugs":{"url":"https://github.com/bogdanrn/yt-embed/issues"},"dist":{"shasum":"ed54c4097e62aa1ec78d772e16aa357a17f6e194","tarball":"https://registry.npmjs.org/@bogdanrn/yt-embed/-/yt-embed-0.2.0.tgz","fileCount":11,"integrity":"sha512-kxS5bCx9A7/IvdYimKR8729n/OKSh56aLlmn9MTgV8J7oIOPVBgL7MYWw4mIxu6WQ0FIr0ChnluzvoObNFR2Cg==","signatures":[{"sig":"MEYCIQC+GloGpq1GypDJ9wDcq+gqTN+J3oTTbS+sV0X5m+9A6QIhAPanDUXIkyXJXzjbJxlKeHDXMaff0fGyDiM36dgO6/Eq","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bogdanrn%2fyt-embed@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":106897},"main":"./dist/index.cjs","type":"module","_from":"file:bogdanrn-yt-embed-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"tsdown --watch","lint":"biome check .","test":"vitest run","build":"tsdown","verify":"pnpm typecheck && pnpm lint && pnpm test && pnpm build","generate":"tsx scripts/generate.ts","lint:fix":"biome check --write .","prebuild":"pnpm generate","typecheck":"tsc --noEmit","docs:build":"typedoc","test:watch":"vitest","postgenerate":"tsx scripts/inject-readme.ts","test:typecheck":"vitest --typecheck --run"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:182b86cd-5ad7-4eef-baaa-0f828f478c70"}},"_resolved":"/tmp/704fb2ba12464a5e7ed5250edc70c6d6/bogdanrn-yt-embed-0.2.0.tgz","_integrity":"sha512-kxS5bCx9A7/IvdYimKR8729n/OKSh56aLlmn9MTgV8J7oIOPVBgL7MYWw4mIxu6WQ0FIr0ChnluzvoObNFR2Cg==","repository":{"url":"git+https://github.com/bogdanrn/yt-embed.git","type":"git"},"_npmVersion":"11.12.1","description":"Promise-wrapped YouTube IFrame Player API","directories":{},"lint-staged":{"*.{js,jsx,ts,tsx,json,jsonc,md}":"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"},"sideEffects":false,"_nodeVersion":"24.15.0","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.21.0","vite":"^8.0.10","jsdom":"^29.1.0","tsdown":"^0.21.10","vitest":"^4.1.5","typedoc":"^0.28.19","ts-morph":"^28.0.0","typescript":"^6.0.3","@types/node":"^25.6.0","lint-staged":"^16.4.0","@biomejs/biome":"^2.4.13","@types/youtube":"^0.2.0","@changesets/cli":"^2.31.0","@commitlint/cli":"^20.5.2","simple-git-hooks":"^2.13.1","@vitest/coverage-v8":"^4.1.5","@commitlint/config-conventional":"^20.5.0"},"simple-git-hooks":{"commit-msg":"pnpm exec commitlint --edit $1","pre-commit":"pnpm exec lint-staged"},"_npmOperationalInternal":{"tmp":"tmp/yt-embed_0.2.0_1777332324735_0.02730753704272182","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@bogdanrn/yt-embed","version":"0.3.0","description":"Promise-wrapped YouTube IFrame Player API","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js","require":"./dist/react/index.cjs"},"./vue":{"types":"./dist/vue/index.d.ts","import":"./dist/vue/index.js","require":"./dist/vue/index.cjs"},"./svelte":{"types":"./dist/svelte/index.d.ts","import":"./dist/svelte/index.js","require":"./dist/svelte/index.cjs"}},"engines":{"node":">=20"},"sideEffects":false,"license":"MIT","author":{"name":"Bogdan Radu"},"repository":{"type":"git","url":"git+https://github.com/bogdanrn/yt-embed.git"},"publishConfig":{"access":"public","provenance":true},"simple-git-hooks":{"pre-commit":"pnpm exec lint-staged","commit-msg":"pnpm exec commitlint --edit $1"},"lint-staged":{"*.{js,jsx,ts,tsx,json,jsonc,md}":"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"},"peerDependencies":{"@types/youtube":"^0.2.0","react":">=18","svelte":">=4","vue":">=3"},"peerDependenciesMeta":{"@types/youtube":{"optional":true},"react":{"optional":true},"svelte":{"optional":true},"vue":{"optional":true}},"devDependencies":{"@biomejs/biome":"^2.4.13","@changesets/cli":"^2.31.0","@commitlint/cli":"^20.5.2","@commitlint/config-conventional":"^20.5.0","@types/node":"^25.6.0","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@types/youtube":"^0.2.0","@vitest/browser":"^4.1.5","@vitest/coverage-v8":"^4.1.5","jsdom":"^29.1.0","lint-staged":"^16.4.0","playwright":"^1.59.1","react":"^19.2.5","react-dom":"^19.2.5","simple-git-hooks":"^2.13.1","svelte":"^5.55.5","ts-morph":"^28.0.0","tsdown":"^0.21.10","tsx":"^4.21.0","typedoc":"^0.28.19","typedoc-plugin-markdown":"^4.11.0","typescript":"^6.0.3","vite":"^8.0.10","vitepress":"^1.6.4","vitest":"^4.1.5","vue":"^3.5.33"},"scripts":{"generate":"tsx scripts/generate.ts","postgenerate":"tsx scripts/inject-readme.ts","prebuild":"pnpm generate","build":"tsdown","dev":"tsdown --watch","typecheck":"tsc --noEmit","lint":"biome check .","lint:fix":"biome check --write .","test":"vitest run --project unit","test:browser":"vitest run --project browser","test:watch":"vitest --project unit","test:typecheck":"vitest --typecheck --run --project unit","docs:api":"typedoc","docs:dev":"vitepress dev docs-site","docs:site":"pnpm docs:api && vitepress build docs-site","docs:preview":"vitepress preview docs-site","docs:build":"pnpm docs:api","verify":"pnpm typecheck && pnpm lint && pnpm test && pnpm build"},"_id":"@bogdanrn/yt-embed@0.3.0","bugs":{"url":"https://github.com/bogdanrn/yt-embed/issues"},"homepage":"https://github.com/bogdanrn/yt-embed#readme","_integrity":"sha512-9IZg1x/7qHC6SLHUBH9nsiG25Gson12eaNLR0KU93LXnT8hO4KEJvKub4hngTkoV3ubDFbCtTlPvHO+FPql3aw==","_resolved":"/tmp/dd03a0c243cb78fd3e3b7aeffdce81cd/bogdanrn-yt-embed-0.3.0.tgz","_from":"file:bogdanrn-yt-embed-0.3.0.tgz","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-9IZg1x/7qHC6SLHUBH9nsiG25Gson12eaNLR0KU93LXnT8hO4KEJvKub4hngTkoV3ubDFbCtTlPvHO+FPql3aw==","shasum":"a9271290d46b326fa5aaac45da47bfa47a999975","tarball":"https://registry.npmjs.org/@bogdanrn/yt-embed/-/yt-embed-0.3.0.tgz","fileCount":43,"unpackedSize":440424,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bogdanrn%2fyt-embed@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCU+oaVeq0nprJr1vnTbWXPdt7EMIefIyJbaEhJmBM/2wIgM49C8Ff/RuwmgHhw5n+rX9yom+MNne3qNNV5KYiYml0="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:182b86cd-5ad7-4eef-baaa-0f828f478c70"}},"directories":{},"maintainers":[{"name":"bogdanrn","email":"bogdanrn@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/yt-embed_0.3.0_1777390144818_0.14447746458644795"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-27T22:35:03.509Z","modified":"2026-04-28T15:29:05.242Z","0.1.0":"2026-04-27T22:35:03.762Z","0.1.1":"2026-04-27T22:51:02.923Z","0.1.2":"2026-04-27T23:16:43.473Z","0.2.0":"2026-04-27T23:25:24.886Z","0.3.0":"2026-04-28T15:29:04.991Z"},"bugs":{"url":"https://github.com/bogdanrn/yt-embed/issues"},"author":{"name":"Bogdan Radu"},"license":"MIT","homepage":"https://github.com/bogdanrn/yt-embed#readme","repository":{"type":"git","url":"git+https://github.com/bogdanrn/yt-embed.git"},"description":"Promise-wrapped YouTube IFrame Player API","maintainers":[{"name":"bogdanrn","email":"bogdanrn@gmail.com"}],"readme":"# @bogdanrn/yt-embed\n\n[![Bundle size](https://img.shields.io/bundlephobia/minzip/@bogdanrn/yt-embed)](https://bundlephobia.com/package/@bogdanrn/yt-embed)\n[![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/bogdanrn/yt-embed?utm_source=oss&utm_medium=github&utm_campaign=bogdanrn%2Fyt-embed&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)](https://coderabbit.ai)\n\nPromise-wrapped YouTube IFrame Player API. TypeScript-first, zero runtime dependencies, `EventTarget`-based. Includes 19 opt-in extensions (timeupdate, mediaSession, cuepoint, persistedState, fullscreen, picture-in-picture, etc.) and first-class hooks for React, Vue, and Svelte.\n\n## Install\n\n```bash\n# pnpm\npnpm add @bogdanrn/yt-embed\n\n# npm\nnpm install @bogdanrn/yt-embed\n\n# yarn\nyarn add @bogdanrn/yt-embed\n\n# bun\nbun add @bogdanrn/yt-embed\n```\n\nTypeScript users should also install `@types/youtube` (optional peer dep) so the global `YT` namespace referenced by the bundled `.d.ts` resolves:\n\n```bash\npnpm add -D @types/youtube\n```\n\n## Quick start\n\n```ts\nimport { YTEmbed } from '@bogdanrn/yt-embed';\n\nconst player = new YTEmbed(document.getElementById('host')!, {\n  videoId: 'M7lc1UVf-VE',\n});\n\nawait player.whenReady();\nawait player.playVideo();\n```\n\n## Cancellation and timeouts\n\n```ts\n// User-controlled timeout via AbortSignal\nawait player.playVideo({\n  awaitState: true,\n  signal: AbortSignal.timeout(3000),\n});\n// Throws on abort.\n\n// User-controlled abort + a timeout, composed\nconst ac = new AbortController();\nawait player.seekTo(120, true, {\n  awaitState: true,\n  signal: AbortSignal.any([ac.signal, AbortSignal.timeout(5000)]),\n});\n```\n\n## Methods\n\n<!-- methods:start -->\n# Methods\n\nAll methods return `Promise`. They auto-queue until the player is ready.\n\n| Method | Source |\n| --- | --- |\n| `addEventListener` | YT.Player.addEventListener |\n| `cuePlaylist` | YT.Player.cuePlaylist |\n| `cueVideoById` | YT.Player.cueVideoById |\n| `cueVideoByUrl` | YT.Player.cueVideoByUrl |\n| `destroy` | YT.Player.destroy |\n| `getAvailablePlaybackRates` | YT.Player.getAvailablePlaybackRates |\n| `getAvailableQualityLevels` | YT.Player.getAvailableQualityLevels |\n| `getCurrentTime` | YT.Player.getCurrentTime |\n| `getDuration` | YT.Player.getDuration |\n| `getIframe` | YT.Player.getIframe |\n| `getPlaybackQuality` | YT.Player.getPlaybackQuality |\n| `getPlaybackRate` | YT.Player.getPlaybackRate |\n| `getPlayerState` | YT.Player.getPlayerState |\n| `getPlaylist` | YT.Player.getPlaylist |\n| `getPlaylistIndex` | YT.Player.getPlaylistIndex |\n| `getSphericalProperties` | YT.Player.getSphericalProperties |\n| `getVideoData` | YT.Player.getVideoData |\n| `getVideoEmbedCode` | YT.Player.getVideoEmbedCode |\n| `getVideoLoadedFraction` | YT.Player.getVideoLoadedFraction |\n| `getVideoUrl` | YT.Player.getVideoUrl |\n| `getVolume` | YT.Player.getVolume |\n| `isMuted` | YT.Player.isMuted |\n| `loadPlaylist` | YT.Player.loadPlaylist |\n| `loadVideoById` | YT.Player.loadVideoById |\n| `loadVideoByUrl` | YT.Player.loadVideoByUrl |\n| `mute` | YT.Player.mute |\n| `nextVideo` | YT.Player.nextVideo |\n| `pauseVideo` | YT.Player.pauseVideo |\n| `playVideo` | YT.Player.playVideo |\n| `playVideoAt` | YT.Player.playVideoAt |\n| `previousVideo` | YT.Player.previousVideo |\n| `removeEventListener` | YT.Player.removeEventListener |\n| `seekTo` | YT.Player.seekTo |\n| `setLoop` | YT.Player.setLoop |\n| `setPlaybackQuality` | YT.Player.setPlaybackQuality |\n| `setPlaybackRate` | YT.Player.setPlaybackRate |\n| `setShuffle` | YT.Player.setShuffle |\n| `setSize` | YT.Player.setSize |\n| `setSphericalProperties` | YT.Player.setSphericalProperties |\n| `setVolume` | YT.Player.setVolume |\n| `stopVideo` | YT.Player.stopVideo |\n| `unMute` | YT.Player.unMute |\n<!-- methods:end -->\n\n## Events\n\nSubscribe via the standard `addEventListener` API. Listeners support `AbortSignal` for clean removal.\n\n```ts\nconst ac = new AbortController();\nplayer.addEventListener('statechange', (e) => {\n  console.log('state:', e.detail.state);\n}, { signal: ac.signal });\nac.abort(); // listener removed\n```\n\n<!-- events:start -->\n# Events\n\nSubscribe via `player.addEventListener(name, handler)`. Names are derived from `YT.Events` callbacks: strip the `on` prefix and lowercase the rest.\n\n| Event name | YT callback |\n| --- | --- |\n| `apichange` | `onApiChange` |\n| `autoplayblocked` | `onAutoplayBlocked` |\n| `error` | `onError` |\n| `playbackqualitychange` | `onPlaybackQualityChange` |\n| `playbackratechange` | `onPlaybackRateChange` |\n| `ready` | `onReady` |\n| `statechange` | `onStateChange` |\n<!-- events:end -->\n\n## Options\n\n| Option | Type | Default | Notes |\n| --- | --- | --- | --- |\n| `videoId` | `string` | — | Initial video. Required for autoplay. |\n| `playerVars` | `YT.PlayerVars` | — | Forwarded to YT. |\n| `extensions` | `Extension[]` | `[]` | See below. |\n| `signal` | `AbortSignal` | — | Aborting calls `destroy()`. |\n| `initTimeoutMs` | `number` | `30000` | Reject `whenReady()` after this. |\n| `pollingIntervalMs` | `number` | `250` | Base cadence for the shared polling ticker (drives time-based extensions). |\n| `privacyMode` | `'standard'` \\| `'enhanced'` | `'standard'` | `'enhanced'` switches the iframe origin to `youtube-nocookie.com` (no cookies until playback). |\n| `isolate` | `boolean` | `false` | Mount the iframe in an internal wrapper so the consumer's element stays stable across destroy. Adapters set this to `true` by default. |\n\n## Extensions\n\nExtensions are tree-shakable named exports. With `\"sideEffects\": false` in this package, only the extensions you import end up in your bundle.\n\n```ts\nimport {\n  YTEmbed,\n  timeUpdateExtension,\n  cuePointExtension,\n  mediaSessionExtension,\n  persistedStateExtension,\n} from '@bogdanrn/yt-embed';\n\nconst player = new YTEmbed(el, {\n  videoId,\n  extensions: [\n    timeUpdateExtension(),\n    cuePointExtension({ cues: [{ time: 30, payload: 'half-way' }] }),\n    mediaSessionExtension(),\n    persistedStateExtension({ key: `pos:${videoId}` }),\n  ],\n});\n\nplayer.addEventListener('cuepoint', (e) => console.log(e.detail));\n```\n\n| Extension | Event(s) emitted | Lifecycle |\n| --- | --- | --- |\n| `timeUpdateExtension` | `timeupdate` | lazy |\n| `durationChangeExtension` | `durationchange` | lazy |\n| `bufferProgressExtension` | `bufferprogress` | lazy |\n| `volumeChangeExtension` | `volumechange` | lazy |\n| `cuePointExtension` | `cuepoint` | lazy |\n| `chapterChangeExtension` | `chapterchange` | lazy |\n| `abLoopExtension` | `loop` | lazy |\n| `scrubBarSyncExtension` | `scrubsync` | lazy |\n| `visibilityChangeExtension` | `visibilitychange` | lazy |\n| `mediaSessionExtension` | `mediasessionupdate` | eager |\n| `persistedStateExtension` | — | eager |\n| `errorRetryExtension` | `retry` | eager |\n| `analyticsExtension` | — (callback sink) | eager |\n| `captionsLanguageExtension` | `captionschange` | eager when `defaultLanguage` set |\n| `headlessAudioExtension` | — | eager |\n| `autoplayPolicyExtension` | `autoplaymuted` | eager |\n| `playbackRateOptimisticExtension` | `playbackratechange` (optimistic) | eager |\n| `seekRangeExtension` | `seekclamped` | eager |\n| `fullscreenExtension` | `fullscreenchange` | eager |\n| `pictureInPictureExtension` | `pipchange` | eager |\n\nPolling extensions share one `setInterval` driven by `pollingIntervalMs` — adding ten polling extensions costs one timer, not ten.\n\n## Framework adapters\n\nEach adapter is a tree-shakable sub-export. Consumers who don't import `/react`, `/vue`, or `/svelte` pay nothing.\n\n### React\n\n```tsx\nimport { useYTEmbed } from '@bogdanrn/yt-embed/react';\n\nexport function Player({ videoId }: { videoId: string }) {\n  const { containerRef, ready, currentTime, duration, isPlaying, error } = useYTEmbed(videoId);\n  return (\n    <div>\n      <div ref={containerRef} />\n      <p>\n        {ready ? `${currentTime.toFixed(1)} / ${duration.toFixed(1)} (${isPlaying ? 'playing' : 'paused'})` : 'Loading…'}\n      </p>\n      {error && <p>{error.message}</p>}\n    </div>\n  );\n}\n```\n\n### Vue\n\n```vue\n<script setup lang=\"ts\">\nimport { useYTEmbed } from '@bogdanrn/yt-embed/vue';\n\nconst props = defineProps<{ videoId: string }>();\nconst { containerRef, ready, currentTime, duration, isPlaying } = useYTEmbed(props.videoId);\n</script>\n\n<template>\n  <div>\n    <div ref=\"containerRef\" />\n    <p v-if=\"ready\">{{ currentTime.toFixed(1) }} / {{ duration.toFixed(1) }} ({{ isPlaying ? 'playing' : 'paused' }})</p>\n  </div>\n</template>\n```\n\n### Svelte\n\n```svelte\n<script lang=\"ts\">\n  import { createYTEmbed } from '@bogdanrn/yt-embed/svelte';\n\n  let { videoId }: { videoId: string } = $props();\n  const { attach, ready, currentTime, duration, isPlaying } = createYTEmbed(videoId);\n</script>\n\n<div use:attach />\n{#if $ready}\n  <p>{$currentTime.toFixed(1)} / {$duration.toFixed(1)} ({$isPlaying ? 'playing' : 'paused'})</p>\n{/if}\n```\n\nAll three adapters auto-include `timeUpdateExtension` and `durationChangeExtension` and set `isolate: true` so the consumer's container ref stays stable across re-renders.\n\n## Content Security Policy\n\nAdd the following directives:\n\n```\nscript-src https://www.youtube.com https://s.ytimg.com\nframe-src  https://www.youtube.com https://www.youtube-nocookie.com\n```\n\nIf you only use `privacyMode: 'enhanced'`, you may omit `https://www.youtube.com` from `frame-src`.\n\n## SSR / Next.js\n\nThe constructor is SSR-safe — no DOM access until first call into the player. `whenReady()` rejects with `EnvironmentError` in non-browser environments, which is what you want for failure-fast diagnostics.\n\nFor the App Router, render the player in a client-only chunk:\n\n```tsx\n'use client';\nimport { useYTEmbed } from '@bogdanrn/yt-embed/react';\n\nexport function YouTubePlayer({ videoId }: { videoId: string }) {\n  const { containerRef } = useYTEmbed(videoId);\n  return <div ref={containerRef} />;\n}\n```\n\n```tsx\n// In the server component\nimport dynamic from 'next/dynamic';\nconst YouTubePlayer = dynamic(() => import('./player').then((m) => m.YouTubePlayer), { ssr: false });\n```\n\n## Cleanup contract\n\n- `destroy()` is idempotent.\n- After `destroy()`, every in-flight wrapper promise rejects with `PlayerDestroyedError`. Pre-ready queued calls reject with `PlayerDestroyedError('Player destroyed before call')`.\n- A `signal.abort()` rejects in-flight calls with `DOMException('Aborted', 'AbortError')` and triggers `destroy()`. Calling `destroy()` afterwards is a no-op.\n- `whenReady()` rejects with `PlayerDestroyedError` if destroyed before init, `PlayerInitError` on timeout, or `EnvironmentError` in SSR.\n\nUse `e.name === 'AbortError'` for caller aborts; `instanceof PlayerInitError` for timeouts; `instanceof PlayerDestroyedError` for destroy-during-init; `instanceof EnvironmentError` for SSR mismatches.\n\n## Multiple concurrent players\n\nThe IFrame API exposes a single `window.onYouTubeIframeAPIReady` slot. This package handles that internally — multiple `YTEmbed` instances created in the same tick share one script load and each receive `onReady` independently.\n\n```ts\nconst a = new YTEmbed(elA, { videoId: 'M7lc1UVf-VE' });\nconst b = new YTEmbed(elB, { videoId: 'tgbNymZ7vqY' });\nawait Promise.all([a.whenReady(), b.whenReady()]);\n```\n\n## API Reference\n\nThe full TypeDoc-generated reference for every exported symbol lives at [`docs/api/README.md`](./docs/api/README.md). Regenerate it with `pnpm docs:build` (typedoc + `typedoc-plugin-markdown`).\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","readmeFilename":"README.md"}