From f877c1f915497d803d4a702bbbc70506f67c4ab4 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 29 Sep 2025 12:37:03 +0200 Subject: [PATCH] Implement VideoView for web --- bun.lock | 40 +++ packages/react-native-video/package.json | 4 + .../src/core/VideoPlayer.web.ts | 18 +- .../src/core/video-view/VideoView.tsx | 64 +--- .../src/core/video-view/VideoView.web.tsx | 302 ++++-------------- .../src/core/video-view/ViewViewProps.ts | 63 ++++ 6 files changed, 182 insertions(+), 309 deletions(-) create mode 100644 packages/react-native-video/src/core/video-view/ViewViewProps.ts diff --git a/bun.lock b/bun.lock index e98023b4..851a3dc7 100644 --- a/bun.lock +++ b/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", @@ -126,6 +129,7 @@ "typescript": "^5.2.2", }, "optionalDependencies": { + "react-native-web": "^0.21.1", "shaka-player": "^4.15.9", }, "peerDependencies": { @@ -1084,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=="], @@ -1544,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=="], @@ -1554,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=="], @@ -1864,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=="], @@ -2086,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=="], @@ -2120,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=="], @@ -2720,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=="], @@ -3100,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=="], @@ -3282,6 +3304,8 @@ "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=="], @@ -3424,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=="], @@ -3476,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=="], @@ -3522,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=="], @@ -3620,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=="], @@ -3640,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=="], @@ -4066,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=="], @@ -4436,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=="], diff --git a/packages/react-native-video/package.json b/packages/react-native-video/package.json index 067ac25c..904e8525 100644 --- a/packages/react-native-video/package.json +++ b/packages/react-native-video/package.json @@ -152,6 +152,10 @@ "version": "0.41.2" }, "optionalDependencies": { + "react-native-web": "^0.21.1", "shaka-player": "^4.15.9" + }, + "dependencies": { + "@types/react-native-web": "^0.19.2" } } diff --git a/packages/react-native-video/src/core/VideoPlayer.web.ts b/packages/react-native-video/src/core/VideoPlayer.web.ts index f77d23a5..fcbac369 100644 --- a/packages/react-native-video/src/core/VideoPlayer.web.ts +++ b/packages/react-native-video/src/core/VideoPlayer.web.ts @@ -1,6 +1,4 @@ -import { Platform } from 'react-native'; -import { NitroModules } from 'react-native-nitro-modules'; -import { type VideoPlayer as VideoPlayerImpl } from '../spec/nitro/VideoPlayer.nitro'; +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'; @@ -13,20 +11,14 @@ import { } from './types/VideoError'; import type { VideoPlayerBase } from './types/VideoPlayerBase'; import type { VideoPlayerStatus } from './types/VideoPlayerStatus'; -import { createPlayer } from './utils/playerFactory'; -import { createSource } from './utils/sourceFactory'; import { VideoPlayerEvents } from './VideoPlayerEvents'; class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { - protected player: VideoPlayerImpl; + protected player = new shaka.Player(); constructor(source: VideoSource | VideoConfig | VideoPlayerSource) { - const hybridSource = createSource(source); - const player = createPlayer(hybridSource); - // Initialize events super(player.eventEmitter); - this.player = player; } /** @@ -35,8 +27,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { * @internal */ __destroy() { - this.clearAllEvents(); - this.player.dispose(); + this.player.destroy(); } /** @@ -153,12 +144,11 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { } set ignoreSilentSwitchMode(value: IgnoreSilentSwitchMode) { - if (__DEV__ && !['ios'].includes(Platform.OS)) { + if (__DEV__) { console.warn( 'ignoreSilentSwitchMode is not supported on this platform, it wont have any effect' ); } - this.player.ignoreSilentSwitchMode = value; } diff --git a/packages/react-native-video/src/core/video-view/VideoView.tsx b/packages/react-native-video/src/core/video-view/VideoView.tsx index 8a78e8d8..1bff2bf5 100644 --- a/packages/react-native-video/src/core/video-view/VideoView.tsx +++ b/packages/react-native-video/src/core/video-view/VideoView.tsx @@ -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, 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 = diff --git a/packages/react-native-video/src/core/video-view/VideoView.web.tsx b/packages/react-native-video/src/core/video-view/VideoView.web.tsx index 8a78e8d8..b5b71560 100644 --- a/packages/react-native-video/src/core/video-view/VideoView.web.tsx +++ b/packages/react-native-video/src/core/video-view/VideoView.web.tsx @@ -1,110 +1,20 @@ -import * as React from 'react'; -import type { ViewProps, 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'; +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"; -export interface VideoViewProps extends Partial, 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; -} - -let nitroIdCounter = 1; -const VideoViewViewManagerFactory = - NitroModules.createHybridObject( - 'VideoViewViewManagerFactory' - ); - -const wrapNativeViewManagerFunction = ( - manager: VideoViewViewManager | null, - func: (manager: VideoViewViewManager) => T -) => { - try { - if (manager === null) { - throw new VideoError('view/not-found', 'View manager not found'); - } - - return func(manager); - } catch (error) { - throw tryParseNativeVideoError(error); - } -}; - -const updateProps = (manager: VideoViewViewManager, props: VideoViewProps) => { - manager.player = props.player.__getNativePlayer(); - manager.controls = props.controls ?? false; - manager.pictureInPicture = props.pictureInPicture ?? false; - manager.autoEnterPictureInPicture = props.autoEnterPictureInPicture ?? false; - manager.resizeMode = props.resizeMode ?? 'none'; - manager.onPictureInPictureChange = props.onPictureInPictureChange; - manager.onFullscreenChange = props.onFullscreenChange; - manager.willEnterFullscreen = props.willEnterFullscreen; - manager.willExitFullscreen = props.willExitFullscreen; - manager.willEnterPictureInPicture = props.willEnterPictureInPicture; - manager.willExitPictureInPicture = props.willExitPictureInPicture; - manager.keepScreenAwake = props.keepScreenAwake ?? true; -}; +const Video = ( + props: Omit, keyof ViewProps> & ViewProps, +) => unstable_createElement("video", props); /** * VideoView is a component that allows you to display a video from a {@link VideoPlayer}. @@ -117,135 +27,61 @@ const updateProps = (manager: VideoViewViewManager, props: VideoViewProps) => { * 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 = React.forwardRef( - ( - { - player, - controls = false, - pictureInPicture = false, - autoEnterPictureInPicture = false, - resizeMode = 'none', - ...props - }, - ref - ) => { - const nitroId = React.useMemo(() => nitroIdCounter++, []); - const nitroViewManager = React.useRef(null); +const VideoView = forwardRef( + ( + { + player, + controls = false, + resizeMode = "none", + style, + // auto pip is unsupported + pictureInPicture = false, + autoEnterPictureInPicture = false, + keepScreenAwake = true, + ...props + }, + ref, + ) => { + const vRef = useRef(null); + useEffect(() => { + const webPlayer = player as unknown as VideoPlayer; + if (vRef.current) webPlayer.__getNativePlayer().attach(vRef.current); + }, [player]); - const setupViewManager = React.useCallback( - (id: number) => { - try { - if (nitroViewManager.current === null) { - nitroViewManager.current = - VideoViewViewManagerFactory.createViewManager(id); + useImperativeHandle( + ref, + () => ({ + enterFullscreen: () => { + vRef.current?.requestFullscreen({ navigationUI: "hide" }); + }, + exitFullscreen: () => { + document.exitFullscreen(); + }, + enterPictureInPicture: () => { + vRef.current?.requestPictureInPicture(); + }, + exitPictureInPicture: () => { + document.exitPictureInPicture(); + }, + canEnterPictureInPicture: () => document.pictureInPictureEnabled, + }), + [], + ); - // Should never happen - if (!nitroViewManager.current) { - throw new VideoError( - 'view/not-found', - 'Failed to create View Manager' - ); - } - } - - // Updates props to native view - updateProps(nitroViewManager.current, { - ...props, - player: player, - controls: controls, - pictureInPicture: pictureInPicture, - autoEnterPictureInPicture: autoEnterPictureInPicture, - resizeMode: resizeMode, - }); - } catch (error) { - throw tryParseNativeVideoError(error); - } - }, - [ - props, - player, - controls, - pictureInPicture, - autoEnterPictureInPicture, - resizeMode, - ] - ); - - const onNitroIdChange = React.useCallback( - (event: { nativeEvent: { nitroId: number } }) => { - setupViewManager(event.nativeEvent.nitroId); - }, - [setupViewManager] - ); - - React.useImperativeHandle( - ref, - () => ({ - enterFullscreen: () => { - wrapNativeViewManagerFunction(nitroViewManager.current, (manager) => { - manager.enterFullscreen(); - }); - }, - exitFullscreen: () => { - wrapNativeViewManagerFunction(nitroViewManager.current, (manager) => { - manager.exitFullscreen(); - }); - }, - enterPictureInPicture: () => { - wrapNativeViewManagerFunction(nitroViewManager.current, (manager) => { - manager.enterPictureInPicture(); - }); - }, - exitPictureInPicture: () => { - wrapNativeViewManagerFunction(nitroViewManager.current, (manager) => { - manager.exitPictureInPicture(); - }); - }, - canEnterPictureInPicture: () => { - return wrapNativeViewManagerFunction( - nitroViewManager.current, - (manager) => { - return manager.canEnterPictureInPicture(); - } - ); - }, - }), - [] - ); - - React.useEffect(() => { - if (!nitroViewManager.current) { - return; - } - - // Updates props to native view - updateProps(nitroViewManager.current, { - ...props, - player: player, - controls: controls, - pictureInPicture: pictureInPicture, - autoEnterPictureInPicture: autoEnterPictureInPicture, - resizeMode: resizeMode, - }); - }, [ - player, - controls, - pictureInPicture, - autoEnterPictureInPicture, - resizeMode, - props, - ]); - - return ( - - ); - } + return ( +