7 Commits

18 changed files with 621 additions and 290 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2

5
biome.json Normal file
View File

@@ -0,0 +1,5 @@
{
"formatter": {
"useEditorconfig": true
}
}

View File

@@ -108,6 +108,9 @@
"packages/react-native-video": {
"name": "react-native-video",
"version": "7.0.0-alpha.4",
"dependencies": {
"@types/react-native-web": "^0.19.2",
},
"devDependencies": {
"@expo/config-plugins": "^10.0.2",
"@react-native/eslint-config": "^0.77.0",
@@ -122,8 +125,13 @@
"react-native": "^0.77.0",
"react-native-builder-bob": "^0.40.0",
"react-native-nitro-modules": "^0.28.0",
"shaka-player": "^4.15.9",
"typescript": "^5.2.2",
},
"optionalDependencies": {
"react-native-web": "^0.21.1",
"shaka-player": "^4.15.9",
},
"peerDependencies": {
"react": "*",
"react-native": "*",
@@ -1080,6 +1088,8 @@
"@types/react": ["@types/react@18.3.20", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg=="],
"@types/react-native-web": ["@types/react-native-web@0.19.2", "", { "dependencies": { "@types/react": "*", "react-native": "*" } }, "sha512-TAgyUi1XcYYUM5j3IwBDufPUD0NaOJiz8ftld6oJPtuIT5lc+uRZZmTcTdflpsVzCtiZ6AjSyLFZ/xD0OtCfJA=="],
"@types/react-router": ["@types/react-router@5.1.20", "", { "dependencies": { "@types/history": "^4.7.11", "@types/react": "*" } }, "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q=="],
"@types/react-router-config": ["@types/react-router-config@5.0.11", "", { "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router": "^5.1.0" } }, "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw=="],
@@ -1540,6 +1550,8 @@
"create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
"cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"crypto-random-string": ["crypto-random-string@4.0.0", "", { "dependencies": { "type-fest": "^1.0.1" } }, "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA=="],
@@ -1550,6 +1562,8 @@
"css-has-pseudo": ["css-has-pseudo@7.0.2", "", { "dependencies": { "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4" } }, "sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ=="],
"css-in-js-utils": ["css-in-js-utils@3.1.0", "", { "dependencies": { "hyphenate-style-name": "^1.0.3" } }, "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A=="],
"css-loader": ["css-loader@6.11.0", "", { "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g=="],
"css-minimizer-webpack-plugin": ["css-minimizer-webpack-plugin@5.0.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "cssnano": "^6.0.1", "jest-worker": "^29.4.3", "postcss": "^8.4.24", "schema-utils": "^4.0.1", "serialize-javascript": "^6.0.1" }, "peerDependencies": { "webpack": "^5.0.0" } }, "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg=="],
@@ -1860,6 +1874,10 @@
"fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
"fbjs": ["fbjs@3.0.5", "", { "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^1.0.35" } }, "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg=="],
"fbjs-css-vars": ["fbjs-css-vars@1.0.2", "", {}, "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="],
"feed": ["feed@4.2.2", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ=="],
"figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="],
@@ -2082,6 +2100,8 @@
"human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
"hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"icss-utils": ["icss-utils@5.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="],
@@ -2116,6 +2136,8 @@
"inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
"inline-style-prefixer": ["inline-style-prefixer@7.0.1", "", { "dependencies": { "css-in-js-utils": "^3.1.0" } }, "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw=="],
"inquirer": ["inquirer@9.3.2", "", { "dependencies": { "@inquirer/figures": "^1.0.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "external-editor": "^3.1.0", "mute-stream": "1.0.0", "ora": "^5.4.1", "run-async": "^3.0.0", "rxjs": "^7.8.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.1" } }, "sha512-+ynEbhWKhyomnaX0n2aLIMSkgSlGB5RrWbNXnEqj6mdaIydu6y40MdBjL38SAB0JcdmOaIaMua1azdjLEr3sdw=="],
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
@@ -2716,6 +2738,8 @@
"node-emoji": ["node-emoji@2.2.0", "", { "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", "emojilib": "^2.4.0", "skin-tone": "^2.0.0" } }, "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"node-forge": ["node-forge@1.3.1", "", {}, "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="],
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
@@ -3096,6 +3120,8 @@
"react-native-video-example": ["react-native-video-example@workspace:example"],
"react-native-web": ["react-native-web@0.21.1", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-BeNsgwwe4AXUFPAoFU+DKjJ+CVQa3h54zYX77p7GVZrXiiNo3vl03WYDYVEy5R2J2HOPInXtQZB5gmj3vuzrKg=="],
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
"react-router": ["react-router@5.3.4", "", { "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", "path-to-regexp": "^1.7.0", "prop-types": "^15.6.2", "react-is": "^16.6.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" }, "peerDependencies": { "react": ">=15" } }, "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA=="],
@@ -3278,8 +3304,12 @@
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shaka-player": ["shaka-player@4.15.9", "", {}, "sha512-x8TElKG1eaj3nlcO9whvH1SbGvJ1gwSdgYlbvcJBNWsMJal70C2ZLk4w8qi8uXpcDjzSR8p28gYgD7WfOdHDGA=="],
"shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="],
"shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="],
@@ -3418,6 +3448,8 @@
"stylehacks": ["stylehacks@6.1.1", "", { "dependencies": { "browserslist": "^4.23.0", "postcss-selector-parser": "^6.0.16" }, "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg=="],
"styleq": ["styleq@0.1.3", "", {}, "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA=="],
"sudo-prompt": ["sudo-prompt@9.2.1", "", {}, "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -3470,6 +3502,8 @@
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trim-newlines": ["trim-newlines@4.1.1", "", {}, "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ=="],
@@ -3516,6 +3550,8 @@
"typescript-eslint": ["typescript-eslint@8.29.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.29.1", "@typescript-eslint/parser": "8.29.1", "@typescript-eslint/utils": "8.29.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w=="],
"ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="],
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
@@ -3614,6 +3650,8 @@
"web-namespaces": ["web-namespaces@1.1.4", "", {}, "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"webpack": ["webpack@5.99.9", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.2", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg=="],
"webpack-bundle-analyzer": ["webpack-bundle-analyzer@4.10.2", "", { "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", "commander": "^7.2.0", "debounce": "^1.2.1", "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { "webpack-bundle-analyzer": "lib/bin/analyzer.js" } }, "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw=="],
@@ -3634,6 +3672,8 @@
"whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"when-exit": ["when-exit@2.1.4", "", {}, "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
@@ -4060,6 +4100,8 @@
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
"figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
"file-loader/schema-utils": ["schema-utils@3.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg=="],
@@ -4430,6 +4472,10 @@
"react-native-video-example/@react-native/eslint-config": ["@react-native/eslint-config@0.77.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", "@react-native/eslint-plugin": "0.77.2", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-ft-flow": "^2.0.1", "eslint-plugin-jest": "^27.9.0", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-native": "^4.0.0" }, "peerDependencies": { "eslint": ">=8", "prettier": ">=2" } }, "sha512-buxBnJU0YLxdkUqn85ZG7BoCjSEjia4HMcnl4X81UQSLM8Z0xCL01QeqHhxxfhYFFkiHwsJILBgHEZizx/hsdQ=="],
"react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
"react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
"react-router/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"read-package-up/read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="],

View File

@@ -16,7 +16,7 @@
"allowUnusedLabels": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ESNext"],
"lib": ["ESNext", "dom"],
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true,
@@ -33,4 +33,4 @@
"target": "ESNext",
"verbatimModuleSyntax": true
}
}
}

View File

@@ -87,17 +87,17 @@ import { VideoPlayer } from 'react-native-video';
const player = new VideoPlayer('https://example.com/video.mp4');
player.onLoad = (data) => {
player.addEventListener('onLoad', (data) => {
console.log('Video loaded! Duration:', data.duration);
};
});
player.onProgress = (data) => {
player.addEventListener('onProgress', (data) => {
console.log('Current time:', data.currentTime);
};
});
player.onError = (error) => {
player.addEventListener('onError', (error) => {
console.error('Player Error:', error.code, error.message);
};
});
player.play();
```
@@ -105,4 +105,4 @@ player.play();
## Clearing Events
- The `player.clearEvent(eventName)` method can be used to clear a specific native event handler.
- When a player instance is no longer needed and `player.release()` is called, all event listeners are automatically cleared
- When a player instance is no longer needed and `player.release()` is called, all event listeners are automatically cleared

View File

@@ -75,6 +75,7 @@
},
"devDependencies": {
"@expo/config-plugins": "^10.0.2",
"@react-native/eslint-config": "^0.77.0",
"@types/react": "^18.2.44",
"del-cli": "^5.1.0",
"eslint": "^8.51.0",
@@ -84,7 +85,7 @@
"prettier": "^3.0.3",
"react": "18.3.1",
"react-native": "^0.77.0",
"@react-native/eslint-config": "^0.77.0",
"shaka-player": "^4.15.9",
"react-native-builder-bob": "^0.40.0",
"react-native-nitro-modules": "^0.28.0",
"typescript": "^5.2.2"
@@ -149,5 +150,12 @@
"type": "view-legacy",
"languages": "kotlin-swift",
"version": "0.41.2"
},
"optionalDependencies": {
"react-native-web": "^0.21.1",
"shaka-player": "^4.15.9"
},
"dependencies": {
"@types/react-native-web": "^0.19.2"
}
}

View File

@@ -20,8 +20,6 @@ import { VideoPlayerEvents } from './VideoPlayerEvents';
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
protected player: VideoPlayerImpl;
public onError?: (error: VideoRuntimeError) => void = undefined;
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
const hybridSource = createSource(source);
const player = createPlayer(hybridSource);
@@ -57,8 +55,10 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
private throwError(error: unknown) {
const parsedError = tryParseNativeVideoError(error);
if (parsedError instanceof VideoRuntimeError && this.onError) {
this.onError(parsedError);
if (
parsedError instanceof VideoRuntimeError
&& this.triggerEvent('onError', parsedError)
) {
// We don't throw errors if onError is provided
return;
}

View File

@@ -0,0 +1,268 @@
import shaka from 'shaka-player';
import type { VideoPlayerSource } from '../spec/nitro/VideoPlayerSource.nitro';
import type { IgnoreSilentSwitchMode } from './types/IgnoreSilentSwitchMode';
import type { MixAudioMode } from './types/MixAudioMode';
import type { TextTrack } from './types/TextTrack';
import type { NoAutocomplete } from './types/Utils';
import type { VideoConfig, VideoSource } from './types/VideoConfig';
import {
tryParseNativeVideoError,
VideoRuntimeError,
} from './types/VideoError';
import type { VideoPlayerBase } from './types/VideoPlayerBase';
import type { VideoPlayerStatus } from './types/VideoPlayerStatus';
import { VideoPlayerEvents } from './VideoPlayerEvents';
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
protected player = new shaka.Player();
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
// Initialize events
super(player.eventEmitter);
}
/**
* Cleans up player's native resources and releases native state.
* After calling this method, the player is no longer usable.
* @internal
*/
__destroy() {
this.player.destroy();
}
/**
* Returns the native (hybrid) player instance.
* Should not be used outside of the module.
* @internal
*/
__getNativePlayer() {
return this.player;
}
/**
* Handles parsing native errors to VideoRuntimeError and calling onError if provided
* @internal
*/
private throwError(error: unknown) {
const parsedError = tryParseNativeVideoError(error);
if (
parsedError instanceof VideoRuntimeError
&& this.triggerEvent('onError', parsedError)
) {
// We don't throw errors if onError is provided
return;
}
throw parsedError;
}
/**
* Wraps a promise to try parsing native errors to VideoRuntimeError
* @internal
*/
private wrapPromise<T>(promise: Promise<T>) {
return new Promise<T>((resolve, reject) => {
promise.then(resolve).catch((error) => {
reject(this.throwError(error));
});
});
}
// Source
get source(): VideoPlayerSource {
return this.player.source;
}
// Status
get status(): VideoPlayerStatus {
return this.player.status;
}
// Duration
get duration(): number {
return this.player.duration;
}
// Volume
get volume(): number {
return this.player.volume;
}
set volume(value: number) {
this.player.volume = value;
}
// Current Time
get currentTime(): number {
return this.player.currentTime;
}
set currentTime(value: number) {
this.player.currentTime = value;
}
// Muted
get muted(): boolean {
return this.player.muted;
}
set muted(value: boolean) {
this.player.muted = value;
}
// Loop
get loop(): boolean {
return this.player.loop;
}
set loop(value: boolean) {
this.player.loop = value;
}
// Rate
get rate(): number {
return this.player.rate;
}
set rate(value: number) {
this.player.rate = value;
}
// Mix Audio Mode
get mixAudioMode(): MixAudioMode {
return this.player.mixAudioMode;
}
set mixAudioMode(value: MixAudioMode) {
this.player.mixAudioMode = value;
}
// Ignore Silent Switch Mode
get ignoreSilentSwitchMode(): IgnoreSilentSwitchMode {
return this.player.ignoreSilentSwitchMode;
}
set ignoreSilentSwitchMode(value: IgnoreSilentSwitchMode) {
if (__DEV__) {
console.warn(
'ignoreSilentSwitchMode is not supported on this platform, it wont have any effect'
);
}
this.player.ignoreSilentSwitchMode = value;
}
// Play In Background
get playInBackground(): boolean {
return this.player.playInBackground;
}
set playInBackground(value: boolean) {
this.player.playInBackground = value;
}
// Play When Inactive
get playWhenInactive(): boolean {
return this.player.playWhenInactive;
}
set playWhenInactive(value: boolean) {
this.player.playWhenInactive = value;
}
// Is Playing
get isPlaying(): boolean {
return this.player.isPlaying;
}
async initialize(): Promise<void> {
await this.wrapPromise(this.player.initialize());
NitroModules.updateMemorySize(this.player);
}
async preload(): Promise<void> {
await this.wrapPromise(this.player.preload());
NitroModules.updateMemorySize(this.player);
}
/**
* Releases the player's native resources and releases native state.
* After calling this method, the player is no longer usable.
* Accessing any properties or methods of the player after calling this method will throw an error.
* If you want to clean player resource use `replaceSourceAsync` with `null` instead.
*/
release(): void {
this.__destroy();
}
play(): void {
try {
this.player.play();
} catch (error) {
this.throwError(error);
}
}
pause(): void {
try {
this.player.pause();
} catch (error) {
this.throwError(error);
}
}
seekBy(time: number): void {
try {
this.player.seekBy(time);
} catch (error) {
this.throwError(error);
}
}
seekTo(time: number): void {
try {
this.player.seekTo(time);
} catch (error) {
this.throwError(error);
}
}
async replaceSourceAsync(
source: VideoSource | VideoConfig | NoAutocomplete<VideoPlayerSource> | null
): Promise<void> {
await this.wrapPromise(
this.player.replaceSourceAsync(
source === null ? null : createSource(source)
)
);
NitroModules.updateMemorySize(this.player);
}
// Text Track Management
getAvailableTextTracks(): TextTrack[] {
try {
return this.player.getAvailableTextTracks();
} catch (error) {
this.throwError(error);
return [];
}
}
selectTextTrack(textTrack: TextTrack | null): void {
try {
this.player.selectTextTrack(textTrack);
} catch (error) {
this.throwError(error);
}
}
// Selected Text Track
get selectedTrack(): TextTrack | undefined {
return this.player.selectedTrack;
}
}
export { VideoPlayer };

View File

@@ -1,10 +1,11 @@
import type { VideoPlayerEventEmitter } from '../spec/nitro/VideoPlayerEventEmitter.nitro';
import type { VideoPlayerEvents as VideoPlayerEventsInterface } from './types/Events';
import type { AllPlayerEvents as PlayerEvents } from './types/Events';
export class VideoPlayerEvents implements VideoPlayerEventsInterface {
export class VideoPlayerEvents {
protected eventEmitter: VideoPlayerEventEmitter;
protected eventListeners: Partial<Record<keyof PlayerEvents, Set<(...params: any[]) => void>>> = {};
protected readonly supportedEvents: (keyof VideoPlayerEventsInterface)[] = [
protected readonly supportedEvents: (keyof PlayerEvents)[] = [
'onAudioBecomingNoisy',
'onAudioFocusChange',
'onBandwidthUpdate',
@@ -28,6 +29,37 @@ export class VideoPlayerEvents implements VideoPlayerEventsInterface {
constructor(eventEmitter: VideoPlayerEventEmitter) {
this.eventEmitter = eventEmitter;
for (let event of this.supportedEvents){
// @ts-expect-error we narrow the type of the event
this.eventEmitter[event] = this.triggerEvent.bind(this, event);
}
}
protected triggerEvent<Event extends keyof PlayerEvents>(
event: Event,
...params: Parameters<PlayerEvents[Event]>
): boolean {
if (!this.eventListeners[event]?.size)
return false;
for (let fn of this.eventListeners[event]) {
fn(...params);
}
return true;
}
addEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event]
) {
this.eventListeners[event] ??= new Set<PlayerEvents[Event]>();
this.eventListeners[event].add(callback);
}
removeEventListener<Event extends keyof PlayerEvents>(
event: Event,
callback: PlayerEvents[Event]
) {
this.eventListeners[event]!.delete(callback);
}
/**
@@ -43,177 +75,7 @@ export class VideoPlayerEvents implements VideoPlayerEventsInterface {
* Clears a specific event from the event emitter.
* @param event - The name of the event to clear.
*/
clearEvent(event: keyof VideoPlayerEventsInterface) {
this.eventEmitter[event] = VideoPlayerEvents.NOOP;
}
static NOOP = () => {};
set onAudioBecomingNoisy(
value: VideoPlayerEventsInterface['onAudioBecomingNoisy']
) {
this.eventEmitter.onAudioBecomingNoisy = value;
}
get onAudioBecomingNoisy(): VideoPlayerEventsInterface['onAudioBecomingNoisy'] {
return this.eventEmitter.onAudioBecomingNoisy;
}
set onAudioFocusChange(
value: VideoPlayerEventsInterface['onAudioFocusChange']
) {
this.eventEmitter.onAudioFocusChange = value;
}
get onAudioFocusChange(): VideoPlayerEventsInterface['onAudioFocusChange'] {
return this.eventEmitter.onAudioFocusChange;
}
set onBandwidthUpdate(
value: VideoPlayerEventsInterface['onBandwidthUpdate']
) {
this.eventEmitter.onBandwidthUpdate = value;
}
get onBandwidthUpdate(): VideoPlayerEventsInterface['onBandwidthUpdate'] {
return this.eventEmitter.onBandwidthUpdate;
}
set onBuffer(value: VideoPlayerEventsInterface['onBuffer']) {
this.eventEmitter.onBuffer = value;
}
get onBuffer(): VideoPlayerEventsInterface['onBuffer'] {
return this.eventEmitter.onBuffer;
}
set onControlsVisibleChange(
value: VideoPlayerEventsInterface['onControlsVisibleChange']
) {
this.eventEmitter.onControlsVisibleChange = value;
}
get onControlsVisibleChange(): VideoPlayerEventsInterface['onControlsVisibleChange'] {
return this.eventEmitter.onControlsVisibleChange;
}
set onEnd(value: VideoPlayerEventsInterface['onEnd']) {
this.eventEmitter.onEnd = value;
}
get onEnd(): VideoPlayerEventsInterface['onEnd'] {
return this.eventEmitter.onEnd;
}
set onExternalPlaybackChange(
value: VideoPlayerEventsInterface['onExternalPlaybackChange']
) {
this.eventEmitter.onExternalPlaybackChange = value;
}
get onExternalPlaybackChange(): VideoPlayerEventsInterface['onExternalPlaybackChange'] {
return this.eventEmitter.onExternalPlaybackChange;
}
set onLoad(value: VideoPlayerEventsInterface['onLoad']) {
this.eventEmitter.onLoad = value;
}
get onLoad(): VideoPlayerEventsInterface['onLoad'] {
return this.eventEmitter.onLoad;
}
set onLoadStart(value: VideoPlayerEventsInterface['onLoadStart']) {
this.eventEmitter.onLoadStart = value;
}
get onLoadStart(): VideoPlayerEventsInterface['onLoadStart'] {
return this.eventEmitter.onLoadStart;
}
set onPlaybackStateChange(
value: VideoPlayerEventsInterface['onPlaybackStateChange']
) {
this.eventEmitter.onPlaybackStateChange = value;
}
get onPlaybackStateChange(): VideoPlayerEventsInterface['onPlaybackStateChange'] {
return this.eventEmitter.onPlaybackStateChange;
}
set onPlaybackRateChange(
value: VideoPlayerEventsInterface['onPlaybackRateChange']
) {
this.eventEmitter.onPlaybackRateChange = value;
}
get onPlaybackRateChange(): VideoPlayerEventsInterface['onPlaybackRateChange'] {
return this.eventEmitter.onPlaybackRateChange;
}
set onProgress(value: VideoPlayerEventsInterface['onProgress']) {
this.eventEmitter.onProgress = value;
}
get onProgress(): VideoPlayerEventsInterface['onProgress'] {
return this.eventEmitter.onProgress;
}
set onReadyToDisplay(value: VideoPlayerEventsInterface['onReadyToDisplay']) {
this.eventEmitter.onReadyToDisplay = value;
}
get onReadyToDisplay(): VideoPlayerEventsInterface['onReadyToDisplay'] {
return this.eventEmitter.onReadyToDisplay;
}
set onSeek(value: VideoPlayerEventsInterface['onSeek']) {
this.eventEmitter.onSeek = value;
}
get onSeek(): VideoPlayerEventsInterface['onSeek'] {
return this.eventEmitter.onSeek;
}
set onStatusChange(value: VideoPlayerEventsInterface['onStatusChange']) {
this.eventEmitter.onStatusChange = value;
}
get onStatusChange(): VideoPlayerEventsInterface['onStatusChange'] {
return this.eventEmitter.onStatusChange;
}
set onTimedMetadata(value: VideoPlayerEventsInterface['onTimedMetadata']) {
this.eventEmitter.onTimedMetadata = value;
}
get onTimedMetadata(): VideoPlayerEventsInterface['onTimedMetadata'] {
return this.eventEmitter.onTimedMetadata;
}
set onTextTrackDataChanged(
value: VideoPlayerEventsInterface['onTextTrackDataChanged']
) {
this.eventEmitter.onTextTrackDataChanged = value;
}
get onTextTrackDataChanged(): VideoPlayerEventsInterface['onTextTrackDataChanged'] {
return this.eventEmitter.onTextTrackDataChanged;
}
set onTrackChange(value: VideoPlayerEventsInterface['onTrackChange']) {
this.eventEmitter.onTrackChange = value;
}
get onTrackChange(): VideoPlayerEventsInterface['onTrackChange'] {
return this.eventEmitter.onTrackChange;
}
set onVolumeChange(value: VideoPlayerEventsInterface['onVolumeChange']) {
this.eventEmitter.onVolumeChange = value;
}
get onVolumeChange(): VideoPlayerEventsInterface['onVolumeChange'] {
return this.eventEmitter.onVolumeChange;
clearEvent(event: keyof PlayerEvents) {
this.eventListeners[event]?.clear();
}
}

View File

@@ -1,19 +1,6 @@
import { useEffect } from 'react';
import { VideoPlayer } from '../VideoPlayer';
import { type VideoPlayerEvents } from '../types/Events';
// Omit undefined from events
type NonUndefined<T> = T extends undefined ? never : T;
// Valid events names
type Events = keyof VideoPlayerEvents | 'onError';
// Valid events params
type EventsParams<T extends Events> = T extends keyof VideoPlayerEvents
? // (Native) Events from VideoPlayerEvents
Parameters<VideoPlayerEvents[T]>
: // (JS) Events from Video Player
Parameters<NonUndefined<VideoPlayer[T]>>;
import type { AllPlayerEvents } from '../types/Events';
import type { VideoPlayer } from '../VideoPlayer';
/**
* Attaches an event listener to a `VideoPlayer` instance for a specified event.
@@ -22,22 +9,14 @@ type EventsParams<T extends Events> = T extends keyof VideoPlayerEvents
* @param event - The name of the event to attach the callback to
* @param callback - The callback for the event
*/
export const useEvent = <T extends Events>(
export const useEvent = <T extends keyof AllPlayerEvents>(
player: VideoPlayer,
event: T,
callback: (...args: EventsParams<T>) => void
callback: AllPlayerEvents[T]
) => {
useEffect(() => {
// @ts-expect-error we narrow the type of the event
player[event] = callback;
player.addEventListener(event, callback);
return () => {
if (event === 'onError') {
// onError is not native event, so we can set it to undefined
player.onError = undefined;
} else {
player.clearEvent(event);
}
};
return () => player.removeEventListener(event, callback);
}, [player, event, callback]);
};

View File

@@ -0,0 +1,44 @@
import type { VideoPlayerSource } from '../../spec/nitro/VideoPlayerSource.nitro';
import type { NoAutocomplete } from '../types/Utils';
import type { VideoConfig, VideoSource } from '../types/VideoConfig';
import { isVideoPlayerSource } from '../utils/sourceFactory';
import { VideoPlayer } from '../VideoPlayer';
import { useManagedInstance } from './useManagedInstance';
const sourceEqual = <T extends VideoConfig | VideoSource | VideoPlayerSource>(
a: T,
b?: T
) => {
if (isVideoPlayerSource(a) && isVideoPlayerSource(b)) {
return a.equals(b);
}
return JSON.stringify(a) === JSON.stringify(b);
};
/**
* Creates a `VideoPlayer` instance and manages its lifecycle.
*
* @param source - The source of the video to play
* @param setup - A function to setup the player
* @returns The `VideoPlayer` instance
*/
export const useVideoPlayer = (
source: VideoConfig | VideoSource | NoAutocomplete<VideoPlayerSource>,
setup?: (player: VideoPlayer) => void
) => {
return useManagedInstance(
{
factory: () => {
const player = new VideoPlayer(source);
setup?.(player);
return player;
},
cleanup: (player) => {
player.__destroy();
},
dependenciesEqualFn: sourceEqual,
},
[JSON.stringify(source)]
);
};

View File

@@ -0,0 +1,7 @@
declare module 'shaka-player' {
export = shaka;
}
declare module 'shaka-player/dist/shaka-player.compiled' {
export = shaka;
}

View File

@@ -1,5 +1,6 @@
import type { VideoPlayerSource } from '../../spec/nitro/VideoPlayerSource.nitro';
import type { TextTrack } from './TextTrack';
import type { VideoRuntimeError } from './VideoError';
import type { VideoOrientation } from './VideoOrientation';
import type { VideoPlayerStatus } from './VideoPlayerStatus';
@@ -92,6 +93,11 @@ export interface VideoPlayerEvents {
onStatusChange: (status: VideoPlayerStatus) => void;
}
export interface AllPlayerEvents extends VideoPlayerEvents {
onError: (error: VideoRuntimeError) => void;
}
export interface VideoViewEvents {
/**
* Called when the video view's picture in picture state changes.

View File

@@ -1,74 +1,14 @@
import * as React from 'react';
import type { ViewProps, ViewStyle } from 'react-native';
import type { ViewStyle } from 'react-native';
import { NitroModules } from 'react-native-nitro-modules';
import type {
VideoViewViewManager,
VideoViewViewManagerFactory,
} from '../../spec/nitro/VideoViewViewManager.nitro';
import type { VideoViewEvents } from '../types/Events';
import type { ResizeMode } from '../types/ResizeMode';
import { tryParseNativeVideoError, VideoError } from '../types/VideoError';
import type { VideoPlayer } from '../VideoPlayer';
import { NativeVideoView } from './NativeVideoView';
export interface VideoViewProps extends Partial<VideoViewEvents>, ViewProps {
/**
* The player to play the video - {@link VideoPlayer}
*/
player: VideoPlayer;
/**
* The style of the video view - {@link ViewStyle}
*/
style?: ViewStyle;
/**
* Whether to show the controls. Defaults to false.
*/
controls?: boolean;
/**
* Whether to enable & show the picture in picture button in native controls. Defaults to false.
*/
pictureInPicture?: boolean;
/**
* Whether to automatically enter picture in picture mode when the video is playing. Defaults to false.
*/
autoEnterPictureInPicture?: boolean;
/**
* How the video should be resized to fit the view. Defaults to 'none'.
* - 'contain': Scale the video uniformly (maintain aspect ratio) so that it fits entirely within the view
* - 'cover': Scale the video uniformly (maintain aspect ratio) so that it fills the entire view (may crop)
* - 'stretch': Scale the video to fill the entire view without maintaining aspect ratio
* - 'none': Do not resize the video
*/
resizeMode?: ResizeMode;
/**
* Whether to keep the screen awake while the video view is mounted. Defaults to true.
*/
keepScreenAwake?: boolean;
}
export interface VideoViewRef {
/**
* Enter fullscreen mode
*/
enterFullscreen: () => void;
/**
* Exit fullscreen mode
*/
exitFullscreen: () => void;
/**
* Enter picture in picture mode
*/
enterPictureInPicture: () => void;
/**
* Exit picture in picture mode
*/
exitPictureInPicture: () => void;
/**
* Check if picture in picture mode is supported
* @returns true if picture in picture mode is supported, false otherwise
*/
canEnterPictureInPicture: () => boolean;
}
import type { VideoViewProps, VideoViewRef } from './ViewViewProps';
let nitroIdCounter = 1;
const VideoViewViewManagerFactory =

View File

@@ -0,0 +1,87 @@
import {
forwardRef,
type HTMLProps,
memo,
useEffect,
useImperativeHandle,
useRef,
} from "react";
import type { ViewProps, ViewStyle } from "react-native";
import { unstable_createElement } from "react-native-web";
import { VideoError } from "../types/VideoError";
import type { VideoPlayer } from "../VideoPlayer.web";
import type { VideoViewProps, VideoViewRef } from "./ViewViewProps";
const Video = (
props: Omit<HTMLProps<HTMLVideoElement>, keyof ViewProps> & ViewProps,
) => unstable_createElement("video", props);
/**
* VideoView is a component that allows you to display a video from a {@link VideoPlayer}.
*
* @param player - The player to play the video - {@link VideoPlayer}
* @param controls - Whether to show the controls. Defaults to false.
* @param style - The style of the video view - {@link ViewStyle}
* @param pictureInPicture - Whether to show the picture in picture button. Defaults to false.
* @param autoEnterPictureInPicture - Whether to automatically enter picture in picture mode
* when the video is playing. Defaults to false.
* @param resizeMode - How the video should be resized to fit the view. Defaults to 'none'.
*/
const VideoView = forwardRef<VideoViewRef, VideoViewProps>(
(
{
player,
controls = false,
resizeMode = "none",
style,
// auto pip is unsupported
pictureInPicture = false,
autoEnterPictureInPicture = false,
keepScreenAwake = true,
...props
},
ref,
) => {
const vRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
const webPlayer = player as unknown as VideoPlayer;
if (vRef.current) webPlayer.__getNativePlayer().attach(vRef.current);
}, [player]);
useImperativeHandle(
ref,
() => ({
enterFullscreen: () => {
vRef.current?.requestFullscreen({ navigationUI: "hide" });
},
exitFullscreen: () => {
document.exitFullscreen();
},
enterPictureInPicture: () => {
vRef.current?.requestPictureInPicture();
},
exitPictureInPicture: () => {
document.exitPictureInPicture();
},
canEnterPictureInPicture: () => document.pictureInPictureEnabled,
}),
[],
);
return (
<Video
ref={vRef}
controls={controls}
style={[
style,
{ objectFit: resizeMode === "stretch" ? "fill" : resizeMode },
]}
{...props}
/>
);
},
);
VideoView.displayName = "VideoView";
export default memo(VideoView);

View File

@@ -0,0 +1,63 @@
import type { ViewProps, ViewStyle } from "react-native";
import type { VideoViewEvents } from "../types/Events";
import type { ResizeMode } from "../types/ResizeMode";
import type { VideoPlayer } from "../VideoPlayer";
export interface VideoViewProps extends Partial<VideoViewEvents>, ViewProps {
/**
* The player to play the video - {@link VideoPlayer}
*/
player: VideoPlayer;
/**
* The style of the video view - {@link ViewStyle}
*/
style?: ViewStyle;
/**
* Whether to show the controls. Defaults to false.
*/
controls?: boolean;
/**
* Whether to enable & show the picture in picture button in native controls. Defaults to false.
*/
pictureInPicture?: boolean;
/**
* Whether to automatically enter picture in picture mode when the video is playing. Defaults to false.
*/
autoEnterPictureInPicture?: boolean;
/**
* How the video should be resized to fit the view. Defaults to 'none'.
* - 'contain': Scale the video uniformly (maintain aspect ratio) so that it fits entirely within the view
* - 'cover': Scale the video uniformly (maintain aspect ratio) so that it fills the entire view (may crop)
* - 'stretch': Scale the video to fill the entire view without maintaining aspect ratio
* - 'none': Do not resize the video
*/
resizeMode?: ResizeMode;
/**
* Whether to keep the screen awake while the video view is mounted. Defaults to true.
*/
keepScreenAwake?: boolean;
}
export interface VideoViewRef {
/**
* Enter fullscreen mode
*/
enterFullscreen: () => void;
/**
* Exit fullscreen mode
*/
exitFullscreen: () => void;
/**
* Enter picture in picture mode
*/
enterPictureInPicture: () => void;
/**
* Exit picture in picture mode
*/
exitPictureInPicture: () => void;
/**
* Check if picture in picture mode is supported
* @returns true if picture in picture mode is supported, false otherwise
*/
canEnterPictureInPicture: () => boolean;
}

View File

@@ -1,26 +1,27 @@
export { useEvent } from './core/hooks/useEvent';
export { useVideoPlayer } from './core/hooks/useVideoPlayer';
export * from './core/types/Events';
export type * from './core/types/Events';
export type { IgnoreSilentSwitchMode } from './core/types/IgnoreSilentSwitchMode';
export type { MixAudioMode } from './core/types/MixAudioMode';
export type { ResizeMode } from './core/types/ResizeMode';
export type { TextTrack } from './core/types/TextTrack';
export type { VideoConfig, VideoSource } from './core/types/VideoConfig';
export {
type LibraryError,
type PlayerError,
type SourceError,
type UnknownError,
type VideoComponentError,
type VideoError,
type VideoErrorCode,
type VideoRuntimeError,
type VideoViewError,
export type {
LibraryError,
PlayerError,
SourceError,
UnknownError,
VideoComponentError,
VideoError,
VideoErrorCode,
VideoRuntimeError,
VideoViewError,
} from './core/types/VideoError';
export type { VideoPlayerStatus } from './core/types/VideoPlayerStatus';
export { VideoPlayer } from './core/VideoPlayer';
export {
default as VideoView,
type VideoViewProps,
type VideoViewRef,
} from './core/video-view/VideoView';
export { VideoPlayer } from './core/VideoPlayer';

6
shell.nix Normal file
View File

@@ -0,0 +1,6 @@
{pkgs ? import <nixpkgs> {}}:
pkgs.mkShell {
packages = with pkgs; [
bun
];
}