mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-06 07:16:12 +00:00
Compare commits
7 Commits
feat/web
...
f877c1f915
| Author | SHA1 | Date | |
|---|---|---|---|
| f877c1f915 | |||
| d2b2dac8c6 | |||
| 6db0a9988b | |||
| 2b0901c132 | |||
| f185c3a9cb | |||
| 33be37b207 | |||
| fc8b2964c1 |
9
.editorconfig
Normal file
9
.editorconfig
Normal 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
5
biome.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"formatter": {
|
||||
"useEditorconfig": true
|
||||
}
|
||||
}
|
||||
46
bun.lock
46
bun.lock
@@ -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=="],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
268
packages/react-native-video/src/core/VideoPlayer.web.ts
Normal file
268
packages/react-native-video/src/core/VideoPlayer.web.ts
Normal 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 };
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -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)]
|
||||
);
|
||||
};
|
||||
7
packages/react-native-video/src/core/shaka.d.ts
vendored
Normal file
7
packages/react-native-video/src/core/shaka.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
declare module 'shaka-player' {
|
||||
export = shaka;
|
||||
}
|
||||
|
||||
declare module 'shaka-player/dist/shaka-player.compiled' {
|
||||
export = shaka;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user