mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-06 07:16:12 +00:00
Compare commits
520 Commits
master
...
d52903709b
| Author | SHA1 | Date | |
|---|---|---|---|
| d52903709b | |||
| 3a2ec54eaa | |||
| f877c1f915 | |||
| d2b2dac8c6 | |||
| 6db0a9988b | |||
| 2b0901c132 | |||
| f185c3a9cb | |||
| 33be37b207 | |||
| fc8b2964c1 | |||
|
|
1ec3610aae | ||
|
|
100aefc8d4 | ||
|
|
4052514cf2 | ||
|
|
a97b75b935 | ||
|
|
f929d56a87 | ||
|
|
4ed51e14ca | ||
|
|
b7a4c57d90 | ||
|
|
776bd4ab45 | ||
|
|
5012373b7c | ||
|
|
8e40502172 | ||
|
|
2da34ddfda | ||
|
|
4de93f43f1 | ||
|
|
fce0640b6f | ||
|
|
f48567b307 | ||
|
|
3509b5d72c | ||
|
|
a9adbbea2e | ||
|
|
bce453532a | ||
|
|
38e5f0dcb6 | ||
|
|
bc2a8f0ca6 | ||
|
|
7b8d0b879d | ||
|
|
0387ba66cd | ||
|
|
5b47cf5b61 | ||
|
|
de8b9cb0cc | ||
|
|
0a34889796 | ||
|
|
a561d6b85d | ||
|
|
1e0536ce4f | ||
|
|
e94d16cbcf | ||
|
|
c3c75b6d79 | ||
|
|
0f55a372bf | ||
|
|
2cc3430afb | ||
|
|
791c54e9e9 | ||
|
|
3df1ba56e9 | ||
|
|
4e15523e82 | ||
|
|
b31f8f0732 | ||
|
|
6baa1e4f4a | ||
|
|
146471d23c | ||
|
|
cde3fdb9a4 | ||
|
|
f9b241bfba | ||
|
|
cf34670c0a | ||
|
|
235bc3bea3 | ||
|
|
ddca01fa9f | ||
|
|
20fa017fc0 | ||
|
|
f5df436826 | ||
|
|
bae799523b | ||
|
|
5de9b9a541 | ||
|
|
f92e492923 | ||
|
|
3002768834 | ||
|
|
d9406e9d44 | ||
|
|
41a1d88265 | ||
|
|
4eff3efbf2 | ||
|
|
ad2da2996b | ||
|
|
742ddb3183 | ||
|
|
e743c63a82 | ||
|
|
1d97e63d42 | ||
|
|
35375f47d5 | ||
|
|
afce52c88f | ||
|
|
f0989a51e1 | ||
|
|
c165dfb3b0 | ||
|
|
76a60b853a | ||
|
|
c6793e5aa3 | ||
|
|
47e5a26819 | ||
|
|
474ad9ebed | ||
|
|
bbe7524a33 | ||
|
|
c1bab6b946 | ||
|
|
dcffd29f37 | ||
|
|
047d74de38 | ||
|
|
950dde6a7d | ||
|
|
a72c2d6078 | ||
|
|
7e2ce4b9a7 | ||
|
|
815d677127 | ||
|
|
677b8545e1 | ||
|
|
6adbba4c87 | ||
|
|
811d8169d2 | ||
|
|
30d58d3a39 | ||
|
|
d6d7e714fe | ||
|
|
5f11ab0ca6 | ||
|
|
e1781e2e1a | ||
|
|
85a9a1472f | ||
|
|
2e6ac0d5a0 | ||
|
|
a172eac96f | ||
|
|
90eb8e9154 | ||
|
|
e5314d7c31 | ||
|
|
8de0a93b8b | ||
|
|
cefee73076 | ||
|
|
1f89daa460 | ||
|
|
cf17963709 | ||
|
|
c75be7f08b | ||
|
|
d3c76142df | ||
|
|
ad0ce759f9 | ||
|
|
8d830243a4 | ||
|
|
237cc6e157 | ||
|
|
fa4098ae53 | ||
|
|
69be802292 | ||
|
|
275204f53f | ||
|
|
501be406fc | ||
|
|
8e9e39b667 | ||
|
|
402b1ce059 | ||
|
|
9474bb4fdf | ||
|
|
b13a718393 | ||
|
|
2fff50c697 | ||
|
|
e51873a3a4 | ||
|
|
917b5f9470 | ||
|
|
136a8a7b94 | ||
|
|
4ba3b7a61f | ||
|
|
18527fcc5c | ||
|
|
94967fc4a6 | ||
|
|
b56b647d85 | ||
|
|
b51b579ff0 | ||
|
|
60c3a08ae3 | ||
|
|
79d25bf1bd | ||
|
|
3576a134e6 | ||
|
|
88c20d1c06 | ||
|
|
45fd250adf | ||
|
|
9f03cc5a09 | ||
|
|
9cf780276a | ||
|
|
55449d9ec4 | ||
|
|
9449eb34f3 | ||
|
|
e165cdefa1 | ||
|
|
6c5bb77def | ||
|
|
701f777b6e | ||
|
|
089e938aeb | ||
|
|
fc1e3f4fd1 | ||
|
|
ecfe12aa81 | ||
|
|
49173edd59 | ||
|
|
987be4b293 | ||
|
|
1c95041a4a | ||
|
|
368830954e | ||
|
|
dc4195e339 | ||
|
|
735e35a2ea | ||
|
|
b1b3db3010 | ||
|
|
2bdc0daf2e | ||
|
|
b17c319c32 | ||
|
|
40340467d7 | ||
|
|
ab8ec0699b | ||
|
|
8836362609 | ||
|
|
a849cc19e8 | ||
|
|
41ddc5c27a | ||
|
|
d2e5d9c64e | ||
|
|
697afd52f6 | ||
|
|
f188a7fd48 | ||
|
|
229a5764ea | ||
|
|
4458627051 | ||
|
|
ede502bf74 | ||
|
|
044e08a78d | ||
|
|
d902c1bf43 | ||
|
|
9f02614f5d | ||
|
|
fa20223c44 | ||
|
|
6e6f91517c | ||
|
|
b5103743e8 | ||
|
|
f9d3878ecc | ||
|
|
b11c1f315d | ||
|
|
bc533e53b0 | ||
|
|
9055b14cf4 | ||
|
|
82f5f3d21c | ||
|
|
13a5da8955 | ||
|
|
235c281219 | ||
|
|
057c287f12 | ||
|
|
b8459ca71d | ||
|
|
3edf511f9b | ||
|
|
6697cbf5d0 | ||
|
|
04eec42f1e | ||
|
|
451bafebd7 | ||
|
|
a8ca97f05f | ||
|
|
c8b800a508 | ||
|
|
faac5ad456 | ||
|
|
2905b61a0d | ||
|
|
74b1d5b540 | ||
|
|
ad52668d05 | ||
|
|
638f454a21 | ||
|
|
43955037bb | ||
|
|
3d3eba97e8 | ||
|
|
d90bf47df5 | ||
|
|
d9f92b800e | ||
|
|
8b952e709a | ||
|
|
8dc10fd4b7 | ||
|
|
449dfb62b5 | ||
|
|
6c3af99979 | ||
|
|
6cc1bff167 | ||
|
|
89ee02bdab | ||
|
|
3924b5e295 | ||
|
|
eff8ea24af | ||
|
|
c47d165668 | ||
|
|
424f4eedde | ||
|
|
d31c72fc04 | ||
|
|
69a7bc2d26 | ||
|
|
a735a4a581 | ||
|
|
78770d92f5 | ||
|
|
abc4d76099 | ||
|
|
16fa20411f | ||
|
|
3da4f1ca97 | ||
|
|
7b4bd9a016 | ||
|
|
1033c9d4f3 | ||
|
|
d757a44bb1 | ||
|
|
64c222df44 | ||
|
|
621a80299c | ||
|
|
63c592f7cd | ||
|
|
569a79c510 | ||
|
|
f37dc9e33e | ||
|
|
dd78241b0d | ||
|
|
2b7c215e66 | ||
|
|
daaac9740a | ||
|
|
d934f214f5 | ||
|
|
d277c5e946 | ||
|
|
de81829d73 | ||
|
|
681aed0aed | ||
| 5fa77c4562 | |||
|
|
d45300270e | ||
|
|
0288d61e46 | ||
|
|
f78a407434 | ||
|
|
6080e96e82 | ||
|
|
5b3add4e7a | ||
|
|
f850b7a71c | ||
|
|
3b4bfd3936 | ||
|
|
21e2da3bfb | ||
|
|
5d532f5982 | ||
|
|
324fc2ac65 | ||
|
|
9eb5502076 | ||
|
|
7c7d83b6e5 | ||
|
|
7501880062 | ||
|
|
f04b233a40 | ||
|
|
a8d5841c7c | ||
|
|
352dfbbc9b | ||
|
|
78f4f0480d | ||
|
|
d86adc52f3 | ||
|
|
9a3fcda3b8 | ||
|
|
4c9db2845b | ||
|
|
2c19a4770d | ||
|
|
d1883a7e00 | ||
|
|
d81e6ea31e | ||
|
|
74fb44ddcf | ||
|
|
0820f8167f | ||
|
|
40872f5ea7 | ||
|
|
149924ffcb | ||
|
|
82dc4cf3a0 | ||
|
|
279cc0e5ed | ||
|
|
3ecf324bb3 | ||
|
|
0c6b47f42c | ||
|
|
b11f05f175 | ||
|
|
724b32b434 | ||
|
|
c81eea54d8 | ||
|
|
ae82c83eef | ||
|
|
17dc2c064f | ||
|
|
0e4c95def9 | ||
|
|
c96f7d41f3 | ||
|
|
6fedca0df7 | ||
|
|
892efdd3ab | ||
|
|
7d43d5d3da | ||
|
|
41d3da9146 | ||
|
|
835186a321 | ||
|
|
7f6b500c82 | ||
|
|
1ef2b3a977 | ||
|
|
0538b3b468 | ||
|
|
e57c7bda5d | ||
|
|
24d90e9ec8 | ||
|
|
b74cb59602 | ||
|
|
84a27f3d9f | ||
|
|
7118ba6819 | ||
|
|
2c1fc964bf | ||
|
|
b2fd8d62a1 | ||
|
|
809a730198 | ||
|
|
e18769ab3a | ||
|
|
4a2beaa147 | ||
|
|
bee4123402 | ||
|
|
b871d937a3 | ||
|
|
b66d2fe146 | ||
|
|
22c21ad249 | ||
|
|
9707081ab9 | ||
|
|
d6bae3cd07 | ||
|
|
c51c061f43 | ||
|
|
8b8ebe9410 | ||
|
|
308447a5ba | ||
|
|
89df9d69ff | ||
|
|
fbe570d62f | ||
|
|
2fa6c43615 | ||
|
|
688d98d68f | ||
|
|
3a32d67087 | ||
|
|
7a2b4014f4 | ||
|
|
fb3c0da6af | ||
|
|
451806c547 | ||
|
|
703ed43996 | ||
|
|
9c38d9f4ef | ||
|
|
0576eacfdd | ||
|
|
24c99f03b9 | ||
|
|
ffa5044e23 | ||
|
|
7db7024cb3 | ||
|
|
ca795f298a | ||
|
|
65faba312d | ||
|
|
b05201a9fa | ||
|
|
0a1085ce03 | ||
|
|
41e2bed6b3 | ||
|
|
4611284247 | ||
|
|
1b691f8e81 | ||
|
|
7e222e8fc4 | ||
|
|
736594ed23 | ||
|
|
b7d1cabf72 | ||
|
|
c6ae17e41d | ||
|
|
cd41a1b234 | ||
|
|
899bb822a5 | ||
|
|
6c03d0a700 | ||
|
|
38aa2b057a | ||
|
|
af0302b1b7 | ||
|
|
74c6dd6279 | ||
|
|
22cfd6cead | ||
|
|
08a57a3ba3 | ||
|
|
2348c5e42c | ||
|
|
5abfb0d448 | ||
|
|
fb1d6bdef7 | ||
|
|
6189080c9a | ||
|
|
adbd06e2df | ||
|
|
1ee5811c8e | ||
|
|
ffbc977ff9 | ||
|
|
9f382163d8 | ||
|
|
ab7e02e453 | ||
|
|
76d5e93c17 | ||
|
|
2f70c02cdc | ||
|
|
7611da155f | ||
|
|
8ef2df1bac | ||
|
|
5cd5e5efe7 | ||
|
|
0104fbbbf3 | ||
|
|
4b8d09e61f | ||
|
|
5abc223db8 | ||
|
|
df9ffde5fa | ||
|
|
4e7c64e707 | ||
|
|
bed9b8e4d9 | ||
|
|
79c30767fc | ||
|
|
0a55ace0ca | ||
|
|
f82268be1b | ||
|
|
05623c9f54 | ||
|
|
38bcfa2f6a | ||
|
|
39cf477ceb | ||
|
|
21e78ea2c1 | ||
|
|
c2084c2ace | ||
|
|
de8ade0620 | ||
|
|
f05d190ae6 | ||
|
|
40a72825e6 | ||
|
|
111a5d2163 | ||
|
|
b25e43ee79 | ||
|
|
452e42f1dd | ||
|
|
66dcf32b56 | ||
|
|
08f6caa645 | ||
|
|
25c74e0534 | ||
|
|
1728373d11 | ||
|
|
ccffcfd709 | ||
|
|
a3ecc0108c | ||
|
|
7562669fd6 | ||
|
|
1d6fb29753 | ||
|
|
01a00b12ae | ||
|
|
3c9b1b571a | ||
|
|
7def3ac387 | ||
|
|
de6e71966a | ||
|
|
76c6329110 | ||
|
|
530686ca82 | ||
|
|
df29c2310d | ||
|
|
3f11894c2b | ||
|
|
702a0d9d32 | ||
|
|
99585987ea | ||
|
|
e5a2ee3bd3 | ||
|
|
322d7e993d | ||
|
|
d4f1648681 | ||
|
|
b431d09e2f | ||
|
|
6e1337689a | ||
|
|
a7d834a80a | ||
|
|
91d27a6009 | ||
|
|
3cfb96adb9 | ||
|
|
cfa5984df7 | ||
|
|
c2ce372bcf | ||
|
|
104ee703ba | ||
|
|
91751abc87 | ||
|
|
c1c7a056f1 | ||
|
|
856b1dd58b | ||
|
|
264b57aa2e | ||
|
|
84bb910d10 | ||
|
|
3d6bc9409c | ||
|
|
dc2a2ab863 | ||
|
|
87e7f42042 | ||
|
|
098a754110 | ||
|
|
2d793dbde1 | ||
|
|
cfb5b1cd31 | ||
|
|
3a4a13011a | ||
|
|
016fca8a2a | ||
|
|
c2cd7529fd | ||
|
|
d4a8c24f65 | ||
|
|
ac0a9c3e3a | ||
|
|
ff1e24aaad | ||
|
|
7133c96cac | ||
|
|
adedc052f0 | ||
|
|
a604cd750a | ||
|
|
b698b1837b | ||
|
|
c2ce66ed26 | ||
|
|
5c6dfb26c5 | ||
|
|
8ad1047dc5 | ||
|
|
bdf3e556d8 | ||
|
|
1b51c15348 | ||
|
|
d072aeb451 | ||
|
|
c7f4d7b83b | ||
|
|
e23e02b359 | ||
|
|
5059e7a7f1 | ||
|
|
46e12e0b94 | ||
|
|
c2cc917368 | ||
|
|
c2a14240ad | ||
|
|
5c29b48747 | ||
|
|
ce9a0cdfa2 | ||
|
|
cd28d370d2 | ||
|
|
36c23a5251 | ||
|
|
69bde447b8 | ||
|
|
563b27a166 | ||
|
|
ec42e7d0c4 | ||
|
|
089dc7e032 | ||
|
|
dac0985430 | ||
|
|
bb2404f8ba | ||
|
|
d987525b6d | ||
|
|
cad5c4624c | ||
|
|
dbd7d7aa2c | ||
|
|
94b3da3477 | ||
|
|
c80164ccdf | ||
|
|
e16730de11 | ||
|
|
8fdc52372c | ||
|
|
d272a86f20 | ||
|
|
77f7255c72 | ||
|
|
cd42dd78c9 | ||
|
|
95e6140eea | ||
|
|
3cd7ab60b2 | ||
|
|
d4c9be2ba0 | ||
|
|
d716e1ab2a | ||
|
|
8a57b127d0 | ||
|
|
7b1e1293f6 | ||
|
|
6455380f9e | ||
|
|
66e0ba579b | ||
|
|
324b461527 | ||
|
|
8ee4bccc37 | ||
|
|
464ce7c516 | ||
|
|
219496ff3a | ||
|
|
98b4a75a90 | ||
|
|
aa854fb4ad | ||
|
|
b3f08f6c99 | ||
|
|
9716f4cb36 | ||
|
|
2e623ca0fb | ||
|
|
e420418e8f | ||
|
|
1a48f190f0 | ||
|
|
2d9484499c | ||
|
|
efb338ee2a | ||
|
|
3f63fd076d | ||
|
|
f87a7938c6 | ||
|
|
d25629bb62 | ||
|
|
aef7879dc1 | ||
|
|
b1cd52bc58 | ||
|
|
8e2709ecfd | ||
|
|
1d235a1fea | ||
|
|
627e5da4c3 | ||
|
|
cbdcbf2573 | ||
|
|
8ad4be459b | ||
|
|
c59d00a0f0 | ||
|
|
8eb31e82c7 | ||
|
|
e05da4e9fe | ||
|
|
51e22abfe3 | ||
|
|
4c63988d12 | ||
|
|
edbb3b60aa | ||
|
|
e96c17321f | ||
|
|
ecc946d1c1 | ||
|
|
518a9a93e0 | ||
|
|
1a8295c8bf | ||
|
|
a1090a1639 | ||
|
|
2cd49b53fd | ||
|
|
ce0c113a19 | ||
|
|
d56b251aef | ||
|
|
eec125579f | ||
|
|
e8ce4979b3 | ||
|
|
818f92f654 | ||
|
|
6f61d7f6e6 | ||
|
|
709818f0ef | ||
|
|
c730306e3a | ||
|
|
20d398608a | ||
|
|
efa1c52491 | ||
|
|
b5ccc48476 | ||
|
|
3e3532691a | ||
|
|
042e13c1dc | ||
|
|
1af12f9dfb | ||
|
|
a0bcdb75d3 | ||
|
|
64e3191f73 | ||
|
|
2285eba8f0 | ||
|
|
336b9f0220 | ||
|
|
f815834025 | ||
|
|
60c7a5e57e | ||
|
|
e26afac403 | ||
|
|
e82f9dc24b | ||
|
|
4c7719a3f5 | ||
|
|
453907483d | ||
|
|
e992243305 | ||
|
|
07f71c2fc4 | ||
|
|
d6941392e0 | ||
|
|
051e884c8f | ||
|
|
2a858df8bc | ||
|
|
10b100de44 | ||
|
|
2633f087d2 | ||
|
|
f28bf0ae33 | ||
|
|
1e5af7b526 | ||
|
|
9b66e7fdce | ||
|
|
d3cc0d0c5a | ||
|
|
e87c14a437 | ||
|
|
0fa0086a39 | ||
|
|
182c953597 | ||
|
|
dd3a400689 | ||
|
|
75d370742b | ||
|
|
f10511d953 | ||
|
|
24c1aab3f5 | ||
|
|
0312afc8ea | ||
|
|
38746ff2ba | ||
|
|
89ae8438fa | ||
|
|
bb9e7eb5a5 | ||
|
|
c9a75f3cde | ||
|
|
098358076d |
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
|
||||
@@ -1,2 +0,0 @@
|
||||
examples/
|
||||
lib/
|
||||
13
.eslintrc
13
.eslintrc
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": [
|
||||
"@react-native",
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"requireConfigFile": false
|
||||
}
|
||||
}
|
||||
18
.github/ISSUE_TEMPLATE.md
vendored
18
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,18 +0,0 @@
|
||||
### Current behavior
|
||||
Describe what happens when you encounter this issue.
|
||||
|
||||
### Reproduction steps
|
||||
A 1, 2, 3, etc. list of what's needed to see the issue happen.
|
||||
|
||||
### Expected behavior
|
||||
Describe what you wanted to happen
|
||||
|
||||
### Platform
|
||||
Which player are you experiencing the problem on:
|
||||
* iOS
|
||||
* Android
|
||||
* Windows UWP
|
||||
* Windows WPF
|
||||
|
||||
### Video sample
|
||||
If possible, include a link to the video that has the problem that can be streamed or downloaded from.
|
||||
74
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
74
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -1,74 +0,0 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG]: "
|
||||
labels: ["bug"]
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for taking the time to fill out this bug report!
|
||||
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version are you using?
|
||||
options:
|
||||
- v6 (Beta)
|
||||
- v5 (Stable)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platforms
|
||||
attributes:
|
||||
label: What platforms are you having the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- iOS
|
||||
- Android
|
||||
- Windows
|
||||
- visionOS
|
||||
- Android TV
|
||||
- Apple tvOS
|
||||
|
||||
- type: dropdown
|
||||
id: architecture
|
||||
attributes:
|
||||
label: Architecture
|
||||
description: What architecture are you using?
|
||||
options:
|
||||
- Old architecture
|
||||
- New architecture with interop layer
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
value: "A bug happened!"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: reproduction-repo
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Provide a link to a repository with a reproduction of the bug, this is optional but it will make us to fix the bug faster
|
||||
placeholder: Reproduction Repository
|
||||
value: "repository link"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Tell us how can we reproduce this bug
|
||||
placeholder: Reproduction
|
||||
value: "Step to reproduce this bug are: "
|
||||
validations:
|
||||
required: true
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
11
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,11 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/react-native-video/react-native-video/discussions
|
||||
about: Please ask and answer questions here.
|
||||
- name: Slack Channel
|
||||
url: https://join.slack.com/t/video-dev/shared_invite/zt-24kgmctuv-2z0O9J_v6q_rg~x1RujdOA
|
||||
about: You can find us on the VideoDev Slack in the react-native-video channel.
|
||||
- name: TheWidlarzGroup Discord
|
||||
url: https://discord.gg/7Y6eE62hXM
|
||||
about: Feel free to join our Discord server and ask questions there.
|
||||
52
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
52
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature]: "
|
||||
labels: ["feature"]
|
||||
assignees: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for taking the time to fill out this feature report!
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Tell us your idea and why will concern if we implement it. You can also create a PR 😄
|
||||
placeholder: Tell us your idea!
|
||||
value: "Very cool idea!"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: why-it-is-needed
|
||||
attributes:
|
||||
label: Why it is needed ?
|
||||
description: Tell us your why it is needed!
|
||||
placeholder: Why it is needed ?
|
||||
value: "Because it is cool!"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: possible-implementation
|
||||
attributes:
|
||||
label: Possible implementation
|
||||
description: |
|
||||
Tell us your possible implementation! It really helps if you could describe from a technical POV how this new feature would work, which code it rely on, etc
|
||||
placeholder: How to implement ?
|
||||
value: "Technical POV how to do it"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: code-sample
|
||||
attributes:
|
||||
label: Code sample
|
||||
description: Please show how the new code could work, if doable
|
||||
placeholder: Code sample
|
||||
value: "Code sample"
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,25 +0,0 @@
|
||||
<!--
|
||||
Thanks for opening a PR!
|
||||
Since this is a volunteer project and is very active, anything you can do to reduce the amount of time needed to review and merge your PR is appreciated.
|
||||
The following steps will help get your PR merged quickly:
|
||||
|
||||
- Update the documentation
|
||||
If you've added new functionality, update the README.md with an entry for your prop or event.
|
||||
The entry should be inserted in alphabetical order.
|
||||
|
||||
- Provide an example of how to test the change
|
||||
If the PR requires special testing setup provide all the relevant instructions and files. This may include a sample video file or URL, configuration, or setup steps.
|
||||
|
||||
- Focus the PR on only one area
|
||||
If you're touching multiple different areas that aren't related, break the changes up into multiple PRs.
|
||||
|
||||
- Describe the changes
|
||||
Add a note describing what your PR does. If there is a change to the behavior of the code, explain why it needs to be updated.
|
||||
-->
|
||||
## Summary
|
||||
|
||||
### Motivation
|
||||
|
||||
### Changes
|
||||
|
||||
## Test plan
|
||||
13
.github/actions/setup-bun/action.yml
vendored
13
.github/actions/setup-bun/action.yml
vendored
@@ -11,24 +11,23 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.0.4
|
||||
bun-version: 1.2.19
|
||||
|
||||
- name: Cache dependencies
|
||||
id: bun-cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}-${{ hashFiles('**/package.json') }}
|
||||
key: ${{ runner.os }}-v7-bun-${{ hashFiles('**/bun.lock') }}-${{ hashFiles('**/package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
||||
${{ runner.os }}-bun-
|
||||
${{ runner.os }}-v7-bun-${{ hashFiles('**/bun.lock') }}
|
||||
${{ runner.os }}-v7-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
if: steps.bun-cache.outputs.cache-hit != 'true'
|
||||
run: bun install
|
||||
shell: bash
|
||||
|
||||
34
.github/actions/setup-node/action.yml
vendored
34
.github/actions/setup-node/action.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Setup node_modules
|
||||
description: Setup Node.js and install dependencies
|
||||
|
||||
inputs:
|
||||
working-directory:
|
||||
description: 'working directory for yarn install'
|
||||
default: ./
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache dependencies
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ inputs.working-directory }}/node_modules
|
||||
.yarn/install-state.gz
|
||||
key: ${{ runner.os }}-yarn-${{ inputs.working-directory }}-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-${{ inputs.working-directory }}-${{ hashFiles('yarn.lock') }}-
|
||||
${{ runner.os }}-yarn-${{ inputs.working-directory }}
|
||||
- name: Install dependencies
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn install --immutable --ignore-scripts
|
||||
shell: bash
|
||||
|
||||
60
.github/stale.yml
vendored
60
.github/stale.yml
vendored
@@ -1,60 +0,0 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 3
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: true
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: true
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions. If you are having a similar problem, please open a
|
||||
new issue and reference this one instead of commenting on a stale or closed
|
||||
issue.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
unmarkComment: false
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
closeComment: false
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 50
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: issues
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
||||
80
.github/workflows/build-android.yml
vendored
80
.github/workflows/build-android.yml
vendored
@@ -1,80 +0,0 @@
|
||||
name: Build Android
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/build-android.yml'
|
||||
- 'android/**'
|
||||
- 'examples/basic/android/**'
|
||||
- 'yarn.lock'
|
||||
- 'examples/basic/yarn.lock'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/build-android.yml'
|
||||
- 'android/**'
|
||||
- 'examples/basic/android/**'
|
||||
- 'yarn.lock'
|
||||
- 'examples/basic/yarn.lock'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Android Example App
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
java-package: jdk
|
||||
|
||||
- name: Install node_modules
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
working-directory: examples/basic
|
||||
|
||||
- name: Restore Gradle cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Run Gradle Build for basic example
|
||||
run: cd examples/basic/android && ./gradlew assembleDebug --build-cache && cd ../../..
|
||||
|
||||
build-without-ads:
|
||||
name: Build Android Example App Without Ads
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
java-package: jdk
|
||||
|
||||
- name: Install node_modules
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
working-directory: examples/basic
|
||||
|
||||
- name: Restore Gradle cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Run Gradle Build for basic example
|
||||
run: cd examples/basic/android && export RNV_SAMPLE_ENABLE_ADS=false && ./gradlew assembleDebug --build-cache && cd ../../..
|
||||
166
.github/workflows/build-ios.yml
vendored
166
.github/workflows/build-ios.yml
vendored
@@ -1,166 +0,0 @@
|
||||
name: Build iOS
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/build-ios.yml'
|
||||
- 'ios/**'
|
||||
- '*.podspec'
|
||||
- 'examples/basic/ios/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/build-ios.yml'
|
||||
- 'ios/**'
|
||||
- '*.podspec'
|
||||
- 'examples/basic/ios/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build iOS Example App
|
||||
runs-on: macos-14 # This allow us to use Xcode 15.0.1 which is a lot faster - TODO change to "macos-latest" once it's out of beta
|
||||
defaults:
|
||||
run:
|
||||
working-directory: examples/basic/ios
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install node_modules
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
working-directory: examples/basic
|
||||
|
||||
- name: Restore buildcache
|
||||
uses: mikehardy/buildcache-action@v2
|
||||
continue-on-error: true
|
||||
|
||||
- name: Setup Ruby (bundle)
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.6.10
|
||||
bundler-cache: true
|
||||
|
||||
- name: Restore Pods cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
examples/basic/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
~/.cocoapods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
- name: Install Pods
|
||||
run: pod install
|
||||
- name: Install xcpretty
|
||||
run: gem install xcpretty
|
||||
- name: Build App
|
||||
run: "set -o pipefail && xcodebuild \
|
||||
-derivedDataPath build -UseModernBuildSystem=YES \
|
||||
-workspace videoplayer.xcworkspace \
|
||||
-scheme videoplayer \
|
||||
-sdk iphonesimulator \
|
||||
-configuration Debug \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 14' \
|
||||
build \
|
||||
CODE_SIGNING_ALLOWED=NO | xcpretty"
|
||||
|
||||
build-with-ads:
|
||||
name: Build iOS Example App With Ads
|
||||
runs-on: macos-14 # This allow us to use Xcode 15.0.1 which is a lot faster - TODO change to "macos-latest" once it's out of beta
|
||||
defaults:
|
||||
run:
|
||||
working-directory: examples/basic/ios
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install node_modules
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
working-directory: examples/basic
|
||||
|
||||
- name: Restore buildcache
|
||||
uses: mikehardy/buildcache-action@v2
|
||||
continue-on-error: true
|
||||
|
||||
- name: Setup Ruby (bundle)
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.6.10
|
||||
bundler-cache: true
|
||||
|
||||
- name: Restore Pods cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
examples/basic/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
~/.cocoapods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
- name: Install Pods
|
||||
run: export RNV_SAMPLE_ENABLE_ADS=true && pod install
|
||||
- name: Install xcpretty
|
||||
run: gem install xcpretty
|
||||
- name: Build App
|
||||
run: "set -o pipefail && export RNV_SAMPLE_ENABLE_ADS=true && xcodebuild \
|
||||
-derivedDataPath build -UseModernBuildSystem=YES \
|
||||
-workspace videoplayer.xcworkspace \
|
||||
-scheme videoplayer \
|
||||
-sdk iphonesimulator \
|
||||
-configuration Debug \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 14' \
|
||||
build \
|
||||
CODE_SIGNING_ALLOWED=NO | xcpretty"
|
||||
|
||||
build-with-caching:
|
||||
name: Build iOS Example App With Caching
|
||||
runs-on: macos-14 # This allow us to use Xcode 15.0.1 which is a lot faster - TODO change to "macos-latest" once it's out of beta
|
||||
defaults:
|
||||
run:
|
||||
working-directory: examples/basic/ios
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install node_modules
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
working-directory: examples/basic
|
||||
|
||||
- name: Restore buildcache
|
||||
uses: mikehardy/buildcache-action@v2
|
||||
continue-on-error: true
|
||||
|
||||
- name: Setup Ruby (bundle)
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.6.10
|
||||
bundler-cache: true
|
||||
|
||||
- name: Restore Pods cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
examples/basic/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
~/.cocoapods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
- name: Install Pods
|
||||
run: export RNV_SAMPLE_VIDEO_CACHING=true && pod install
|
||||
- name: Install xcpretty
|
||||
run: gem install xcpretty
|
||||
- name: Build App
|
||||
run: "set -o pipefail && export RNV_SAMPLE_VIDEO_CACHING=true && xcodebuild \
|
||||
-derivedDataPath build -UseModernBuildSystem=YES \
|
||||
-workspace videoplayer.xcworkspace \
|
||||
-scheme videoplayer \
|
||||
-sdk iphonesimulator \
|
||||
-configuration Debug \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 14' \
|
||||
build \
|
||||
CODE_SIGNING_ALLOWED=NO | xcpretty"
|
||||
34
.github/workflows/check-android.yml
vendored
34
.github/workflows/check-android.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Check Android
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/check-android.yml'
|
||||
- 'android/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/check-android.yml'
|
||||
- 'android/**'
|
||||
|
||||
jobs:
|
||||
Kotlin-Lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.1/ktlint && chmod a+x ktlint && sudo mv ktlint /usr/local/bin/
|
||||
- name: run ktlint
|
||||
working-directory: ./android/
|
||||
run: |
|
||||
ktlint --reporter=checkstyle,output=build/ktlint-report.xml --relative --editorconfig=./.editorconfig
|
||||
continue-on-error: true
|
||||
- uses: yutailang0119/action-ktlint@v3
|
||||
with:
|
||||
report-path: ./android/build/*.xml
|
||||
continue-on-error: false
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ktlint-report
|
||||
path: ./android/build/*.xml
|
||||
31
.github/workflows/check-clang.yml
vendored
31
.github/workflows/check-clang.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Check CLang
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/check-clang.yml'
|
||||
- 'ios/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/check-clang.yml'
|
||||
- 'ios/**'
|
||||
|
||||
jobs:
|
||||
CLang-Format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install clang-format
|
||||
run: sudo apt-get install clang-format
|
||||
- name: Check ios clang formatting
|
||||
run: |
|
||||
find ios -type f \( -name "*.h" -o -name "*.cpp" -o -name "*.m" -o -name "*.mm" \) -print0 | while read -d $'\0' file; do
|
||||
clang-format -style=file:./ios/.clang-format -i "$file"
|
||||
done
|
||||
shell: bash
|
||||
- name: Check for changes
|
||||
run: git diff --exit-code HEAD
|
||||
41
.github/workflows/check-ios.yml
vendored
41
.github/workflows/check-ios.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Check iOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/check-ios.yml'
|
||||
- 'ios/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/check-ios.yml'
|
||||
- 'ios/**'
|
||||
|
||||
jobs:
|
||||
Swift-Lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Lint with SwiftLint
|
||||
uses: norio-nomura/action-swiftlint@master
|
||||
with:
|
||||
args: --strict
|
||||
env:
|
||||
WORKING_DIRECTORY: ios
|
||||
Swift-Format:
|
||||
runs-on: macos-14 # This allow us to use Xcode 15.0.1 which is a lot faster - TODO change to "macos-latest" once it's out of beta
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./ios
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install SwiftFormat
|
||||
run: brew install swiftformat
|
||||
|
||||
- name: Format Swift code
|
||||
run: swiftformat --verbose .
|
||||
|
||||
- name: Verify formatted code is unchanged
|
||||
run: git diff --exit-code HEAD
|
||||
62
.github/workflows/check-js.yml
vendored
62
.github/workflows/check-js.yml
vendored
@@ -1,62 +0,0 @@
|
||||
name: Check JS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '.github/workflows/check-js.yml'
|
||||
- 'src/**'
|
||||
- '*.json'
|
||||
- '*.js'
|
||||
- '*.jsx'
|
||||
- '*.ts'
|
||||
- '*.tsx'
|
||||
- '*.lock'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/check-js.yml'
|
||||
- 'src/**'
|
||||
- '*.json'
|
||||
- '*.js'
|
||||
- '*.jsx'
|
||||
- '*.ts'
|
||||
- '*.tsx'
|
||||
- '*.lock'
|
||||
|
||||
jobs:
|
||||
TypeScript-Check:
|
||||
name: Check TS (tsc)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install node_modules
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Install reviewdog
|
||||
uses: reviewdog/action-setup@v1
|
||||
|
||||
- name: Check TypeScript
|
||||
run: |
|
||||
yarn tsc | reviewdog -name="tsc" -efm="%f(%l,%c): error TS%n: %m" -reporter="github-pr-review" -filter-mode="nofilter" -fail-on-error -tee
|
||||
env:
|
||||
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
JS-Lint:
|
||||
name: Lint JS (eslint, prettier)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install node_modules
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Run ESLint
|
||||
run: yarn lint -f @jamesacarr/github-actions
|
||||
|
||||
- name: Run ESLint with auto-fix
|
||||
run: yarn lint --fix
|
||||
|
||||
- name: Verify no files have changed after auto-fix
|
||||
run: git diff --exit-code HEAD
|
||||
62
.github/workflows/deploy-docs.yml
vendored
62
.github/workflows/deploy-docs.yml
vendored
@@ -1,46 +1,50 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
name: deploy docs
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v7
|
||||
paths:
|
||||
# Update on workflow change
|
||||
- '.github/workflows/deploy-docs.yml'
|
||||
# Update on docs change
|
||||
- 'docs/**'
|
||||
# Update on code change (Api Reference)
|
||||
- 'packages/react-native-video/src/**'
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
build:
|
||||
name: Build Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup-bun
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
working-directory: ./docs
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache build
|
||||
uses: actions/cache@v3
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Build Documentation
|
||||
run: bun run --cwd docs build
|
||||
|
||||
- name: Upload Build Artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: |
|
||||
docs/.next/cache
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}-${{ hashFiles('**/package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}
|
||||
${{ runner.os }}-nextjs-
|
||||
path: docs/build
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
bun --cwd docs build
|
||||
touch docs/out/.nojekyll
|
||||
deploy:
|
||||
name: Deploy Documentation to GitHub Pages
|
||||
needs: build
|
||||
|
||||
- name: Deploy docs to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs/out
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
35
.github/workflows/test-build-docs.yml
vendored
35
.github/workflows/test-build-docs.yml
vendored
@@ -1,35 +0,0 @@
|
||||
|
||||
name: Test Docs build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/test-build-docs.yml'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
working-directory: ./docs
|
||||
|
||||
- name: Cache build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
docs/.next/cache
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}-${{ hashFiles('**/package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lockb') }}
|
||||
${{ runner.os }}-nextjs-
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
bun --cwd docs build
|
||||
touch docs/out/.nojekyll
|
||||
27
.github/workflows/test-docs-build.yml
vendored
Normal file
27
.github/workflows/test-docs-build.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Test Documentation Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- v7
|
||||
paths:
|
||||
# Update on workflow change
|
||||
- '.github/workflows/test-docs-build.yml'
|
||||
# Update on docs change
|
||||
- 'docs/**'
|
||||
# Update on code change (Api Reference)
|
||||
- 'packages/react-native-video/src/**'
|
||||
|
||||
jobs:
|
||||
test-docs-deploy:
|
||||
name: Test Documentation Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Test build website
|
||||
run: bun run --cwd docs build
|
||||
63
.gitignore
vendored
63
.gitignore
vendored
@@ -2,6 +2,15 @@
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
**/.xcode.env.local
|
||||
|
||||
# XDE
|
||||
.expo/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
jsconfig.json
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
@@ -21,29 +30,35 @@ DerivedData
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
Pods
|
||||
|
||||
# Android/IJ
|
||||
#
|
||||
*.iml
|
||||
.idea
|
||||
.classpath
|
||||
.cxx
|
||||
.gradle
|
||||
local.properties
|
||||
*.hprof
|
||||
.idea
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
||||
local.properties
|
||||
android.iml
|
||||
|
||||
# Cocoapods
|
||||
#
|
||||
example/ios/Pods
|
||||
|
||||
# Ruby
|
||||
example/vendor/
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
*.log
|
||||
npm-debug.log
|
||||
yarn-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# yarn
|
||||
yarn.lock
|
||||
|
||||
# editor workspace settings
|
||||
.vscode
|
||||
# Bun
|
||||
package-lock.json
|
||||
**/*.bun
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
@@ -51,12 +66,22 @@ buck-out/
|
||||
android/app/libs
|
||||
android/keystores/debug.keystore
|
||||
|
||||
# windows
|
||||
Deploy.binlog
|
||||
msbuild.binlog
|
||||
android/buildOutput_*
|
||||
# Yarn
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# lib build
|
||||
# Expo
|
||||
.expo/
|
||||
|
||||
# Turborepo
|
||||
.turbo/
|
||||
|
||||
# generated by bob
|
||||
lib/
|
||||
!src/lib
|
||||
*.tsbuildinfo
|
||||
|
||||
# TypeScript
|
||||
tsconfig.tsbuildinfo
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": false,
|
||||
"jsxBracketSameLine": true
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"git": {
|
||||
"commitMessage": "chore: release v${version}",
|
||||
"requireCleanWorkingDir": true,
|
||||
"tagAnnotation": "Release v${version}",
|
||||
"tagName": "v${version}"
|
||||
},
|
||||
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": "angular",
|
||||
"infile": "CHANGELOG.md"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"before:init": [
|
||||
"rm -Rf lib tsconfig.tsbuildinfo",
|
||||
"yarn install --frozen-lockfile --non-interactive --production=false",
|
||||
"yarn run lint",
|
||||
"yarn run build"
|
||||
],
|
||||
"after:release": "echo Successfully released ${name} v${version} from repository ${repo.repository}."
|
||||
},
|
||||
"npm": {
|
||||
"skipChecks": false
|
||||
}
|
||||
}
|
||||
502
CHANGELOG.md
502
CHANGELOG.md
@@ -1,502 +0,0 @@
|
||||
|
||||
|
||||
# [6.0.0-beta.6](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.5...v6.0.0-beta.6) (2024-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing node_modules paths to metro.config.js of basic example app ([#3555](https://github.com/react-native-video/react-native-video/issues/3555)) ([d505de5](https://github.com/react-native-video/react-native-video/commit/d505de5910a22ab9a0d7429e6b88a81cd2594b9c))
|
||||
* add missing shutterColor type ([#3561](https://github.com/react-native-video/react-native-video/issues/3561)) ([ba00881](https://github.com/react-native-video/react-native-video/commit/ba00881ddcd53c2f5a4e1fc6e30cb5eb7ef674a3))
|
||||
* **android:** check disableFocus when state is ready ([#3494](https://github.com/react-native-video/react-native-video/issues/3494)) ([366c841](https://github.com/react-native-video/react-native-video/commit/366c841c0b960fd461ae7dcfdcb76a928fadf2b8))
|
||||
* **android:** enableDecoderFallback to decrease DECODER_ERROR issue ([#3416](https://github.com/react-native-video/react-native-video/issues/3416)) ([eaa72c6](https://github.com/react-native-video/react-native-video/commit/eaa72c66659b9e2a22af9ff9d43013521f6a66e3))
|
||||
* **android:** onSeek called instantly ([#3530](https://github.com/react-native-video/react-native-video/issues/3530)) ([af6aea8](https://github.com/react-native-video/react-native-video/commit/af6aea8934e19467e1ed8e21808b2dbddb6f6356))
|
||||
* **android:** suppress lint `PrivateResource` ([#3531](https://github.com/react-native-video/react-native-video/issues/3531)) ([38e3625](https://github.com/react-native-video/react-native-video/commit/38e3625541753340e912e474b753e0f4fac4e9c1))
|
||||
* **docs/ci:** add typescript ([#3572](https://github.com/react-native-video/react-native-video/issues/3572)) ([0f31271](https://github.com/react-native-video/react-native-video/commit/0f31271dcf2bfe2f4429e22040660025be8a6a3c))
|
||||
* **docs:** fix build ([#3571](https://github.com/react-native-video/react-native-video/issues/3571)) ([4fc7d27](https://github.com/react-native-video/react-native-video/commit/4fc7d2788b4d01c581a31cc3ac733c3948b65a3a))
|
||||
* **ios:** add text tracks only if we successfully insertTimeRage ([#3557](https://github.com/react-native-video/react-native-video/issues/3557)) ([b73baad](https://github.com/react-native-video/react-native-video/commit/b73baad2c2c0c6ea701d865eee32d4e94ae58178))
|
||||
* **ios:** apply `cropStart` when in repeat mode ([#3525](https://github.com/react-native-video/react-native-video/issues/3525)) ([2c0e009](https://github.com/react-native-video/react-native-video/commit/2c0e00987685875f9603ae2084ae23b3c1aebce7))
|
||||
* **ios:** current release volume change observer ([#3565](https://github.com/react-native-video/react-native-video/issues/3565)) ([16f3cdb](https://github.com/react-native-video/react-native-video/commit/16f3cdbd9a7864206feaeef29344c09792d66d56))
|
||||
* **ios:** Do not crash when accessLog return nil ([#3549](https://github.com/react-native-video/react-native-video/issues/3549)) ([4d4b56c](https://github.com/react-native-video/react-native-video/commit/4d4b56c05dd3c09fce5ddc38f56b0391c357ac85))
|
||||
* **ios:** don't crop video when in repeat mode ([#3575](https://github.com/react-native-video/react-native-video/issues/3575)) ([90b31af](https://github.com/react-native-video/react-native-video/commit/90b31af2c969b6d6d57877c71ef3a4830a76aedc))
|
||||
* **ios:** ensure playback stopped in background ([#3587](https://github.com/react-native-video/react-native-video/issues/3587)) ([41c6785](https://github.com/react-native-video/react-native-video/commit/41c6785ee8c667ebe9c6c464223f6485473d94f8))
|
||||
* **ios:** fix missing bridge in bridgeless mode ([#3570](https://github.com/react-native-video/react-native-video/issues/3570)) ([46c8c49](https://github.com/react-native-video/react-native-video/commit/46c8c498c474600a0b35ebaf744306aefa42905f))
|
||||
* **ios:** fix tvOS build ([#3524](https://github.com/react-native-video/react-native-video/issues/3524)) ([9306d9a](https://github.com/react-native-video/react-native-video/commit/9306d9a15d281a60492f6d4166598a389a56f652))
|
||||
* **ios:** split licenseUrl and loadedLicenseUrl ([#3578](https://github.com/react-native-video/react-native-video/issues/3578)) ([7c4d19f](https://github.com/react-native-video/react-native-video/commit/7c4d19fa72a35449dd11ec59278b2ea11ec629fc))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **android:** add subtitle event ([#3566](https://github.com/react-native-video/react-native-video/issues/3566)) ([6184c10](https://github.com/react-native-video/react-native-video/commit/6184c10acc90defd63cd55af51458864dfe112d5))
|
||||
* implement opacity to control visibility of subtitles ([#3583](https://github.com/react-native-video/react-native-video/issues/3583)) ([f4cce2e](https://github.com/react-native-video/react-native-video/commit/f4cce2ecdba0668c3ecf74d2fd7956df4dd8489d))
|
||||
* **ios:** Add ios support for accessing WebVTT Subtitle Content ([#3541](https://github.com/react-native-video/react-native-video/issues/3541)) ([253ffb5](https://github.com/react-native-video/react-native-video/commit/253ffb595633a4b18221339278f73c8416225f56))
|
||||
* move require (local files) to `source.uri` ([#3535](https://github.com/react-native-video/react-native-video/issues/3535)) ([41ac781](https://github.com/react-native-video/react-native-video/commit/41ac7814121fc70a123fa4585dc9b1bd96e9629f))
|
||||
|
||||
# [6.0.0-beta.5](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.4...v6.0.0-beta.5) (2024-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **android:** fix crash with interop layer ([#3509](https://github.com/react-native-video/react-native-video/issues/3509)) ([41e9bcb](https://github.com/react-native-video/react-native-video/commit/41e9bcb1ef28c1532863186c83423814fcaf2372))
|
||||
* **android:** re-layout controls after fullscreen dismiss ([#3490](https://github.com/react-native-video/react-native-video/issues/3490)) ([135d97c](https://github.com/react-native-video/react-native-video/commit/135d97ce506bf1a0226042e0f29f4de5bcc10972))
|
||||
* fix typo ([#3497](https://github.com/react-native-video/react-native-video/issues/3497)) ([336eb44](https://github.com/react-native-video/react-native-video/commit/336eb44dc6061dad9cdc3382eb05d0a0effbef64))
|
||||
* **ios:** fix pip memory leak ([#3506](https://github.com/react-native-video/react-native-video/issues/3506)) ([53068dd](https://github.com/react-native-video/react-native-video/commit/53068ddd41218bb615cd129eba2c36d6347ccf25))
|
||||
* remove lifecycle listener after component unmount ([#3489](https://github.com/react-native-video/react-native-video/issues/3489)) ([3858a15](https://github.com/react-native-video/react-native-video/commit/3858a15b4268ae54d5b97c036d86b05aaf31bcf9)), closes [#3488](https://github.com/react-native-video/react-native-video/issues/3488)
|
||||
* remove pausePlayback when audio focus loss event ([#3496](https://github.com/react-native-video/react-native-video/issues/3496)) ([b1ab0f2](https://github.com/react-native-video/react-native-video/commit/b1ab0f24a3efbcc3be49005060f50b34a117664e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* implement onAudioTracks and onTextTracks on ios ([#3503](https://github.com/react-native-video/react-native-video/issues/3503)) ([6a49cba](https://github.com/react-native-video/react-native-video/commit/6a49cba273fa0a47e106f4abb8caeb4ab6dbe4c8))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix: remove pausePlayback when audio focus loss event (#3496)" (#3504) ([aec7db6](https://github.com/react-native-video/react-native-video/commit/aec7db63901c42dd7a591b030bfc69daa8860341)), closes [#3496](https://github.com/react-native-video/react-native-video/issues/3496) [#3504](https://github.com/react-native-video/react-native-video/issues/3504)
|
||||
|
||||
# [6.0.0-beta.4](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.3...v6.0.0-beta.4) (2024-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
* add missing audioOutput prop (#3450) (f20d68b)
|
||||
* **android**: support opacity properly (#3464) (11e5b75)
|
||||
* **ios**: currentPlaybackTime in ms and not seconds (#3472) (3f63c16)
|
||||
* **ios**: remove extra dismissFullscreenPlayer declaration (#3474) (045f5fa)
|
||||
|
||||
### Features
|
||||
* add visionOS support (#3425) (cf3ebb7)
|
||||
* **ios**: migrate from deprecated methods (#3444) (5aaa53d)
|
||||
* **ios**: update the way to get keyWindow (#3448) (f35727f)
|
||||
* **ios**: update timed metadata handler (#3449) (481cc71)
|
||||
|
||||
# [6.0.0-beta.3](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.2...v6.0.0-beta.3) (2023-12-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **android:** default UA ([#3429](https://github.com/react-native-video/react-native-video/issues/3429)) ([dd7bb54](https://github.com/react-native-video/react-native-video/commit/dd7bb54720c06eca045d72e7557d6f472a793b6f))
|
||||
* ensure save doesn't crash on android ([#3415](https://github.com/react-native-video/react-native-video/issues/3415)) ([22a2655](https://github.com/react-native-video/react-native-video/commit/22a2655dca4bb53074ce5a74cfeb7f9bb26b13a3))
|
||||
* **ios:** revert ios url encoding as this breaks encoded urls ([#3440](https://github.com/react-native-video/react-native-video/issues/3440)) ([0723481](https://github.com/react-native-video/react-native-video/commit/0723481fee75890bc2fff967e3b5bc8946e481a3))
|
||||
* **ReactVideoProps:** add accessibility & testID in typing ([#3434](https://github.com/react-native-video/react-native-video/issues/3434)) ([d986b7b](https://github.com/react-native-video/react-native-video/commit/d986b7bf57f8fe49cbf5f507efde4aeb28ee34f8))
|
||||
|
||||
# [6.0.0-beta.2](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.1...v6.0.0-beta.2) (2023-12-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add allowsExternalPlayback missing on ReactVideoProps ([#3398](https://github.com/react-native-video/react-native-video/issues/3398)) ([72679a7](https://github.com/react-native-video/react-native-video/commit/72679a7d639b9c000e060af0dbab7c862c180b00))
|
||||
* **android:** add explicitly dependancy to androidx.activity ([#3410](https://github.com/react-native-video/react-native-video/issues/3410)) ([908e30f](https://github.com/react-native-video/react-native-video/commit/908e30f9b8d950fa1423a10d4b08135b6cc4d43a))
|
||||
* **android:** ensure adTagUrl can be reset ([#3408](https://github.com/react-native-video/react-native-video/issues/3408)) ([f9bcaac](https://github.com/react-native-video/react-native-video/commit/f9bcaac5158ea2d835dd3177b62ad0446eb30d67))
|
||||
* revert drm type definition change ([#3409](https://github.com/react-native-video/react-native-video/issues/3409)) ([fbb5654](https://github.com/react-native-video/react-native-video/commit/fbb5654a8e075a2b33ae17bd322bb79b1f459d53))
|
||||
|
||||
# [6.0.0-beta.1](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.0...v6.0.0-beta.1) (2023-12-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **android:** ads build and enable ads in android sample ([#3376](https://github.com/react-native-video/react-native-video/issues/3376)) ([fe89122](https://github.com/react-native-video/react-native-video/commit/fe89122524826093689118a4515802d83ca88679))
|
||||
* **android:** fix leak caused by removing lifecycle listener too early ([#3380](https://github.com/react-native-video/react-native-video/issues/3380)) ([0c0f317](https://github.com/react-native-video/react-native-video/commit/0c0f3174cb37d3c664a345ea00fcbaafffcd4b10))
|
||||
* **android:** revert media3 update, back to 1.1.1 ([#3369](https://github.com/react-native-video/react-native-video/issues/3369)) ([5beef38](https://github.com/react-native-video/react-native-video/commit/5beef383cba13d3ac471bfde27e4acfaa19adfec))
|
||||
* **ios:** check for ios url query encoding ([#3384](https://github.com/react-native-video/react-native-video/issues/3384)) ([de4159f](https://github.com/react-native-video/react-native-video/commit/de4159f0c2825a58d88f3882215da4bf51fdbeb2))
|
||||
* **ios:** fix pip(when player doesn't fill screen) ([#3363](https://github.com/react-native-video/react-native-video/issues/3363)) ([11f6201](https://github.com/react-native-video/react-native-video/commit/11f62013e33939ce3f78ec7cf40e4da464afa824))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ad:** add data to onReceiveAdEvent ([#3378](https://github.com/react-native-video/react-native-video/issues/3378)) ([d05231d](https://github.com/react-native-video/react-native-video/commit/d05231d76b87e2f65bc7648bfb81d01e4054b2de))
|
||||
* add AdEvent enum to have an exhaustive list of all possible AdEvent values ([#3374](https://github.com/react-native-video/react-native-video/issues/3374)) ([b3744f9](https://github.com/react-native-video/react-native-video/commit/b3744f9b9f25b469fb8b0828e3762842bd5026de))
|
||||
* add onAdError event listener ([#3381](https://github.com/react-native-video/react-native-video/issues/3381)) ([596c02d](https://github.com/react-native-video/react-native-video/commit/596c02d2b3b5175e1653844c39a47ecfd5e23163))
|
||||
* **android:** bump media3 version from v1.1.1 to v1.2.0 ([#3362](https://github.com/react-native-video/react-native-video/issues/3362)) ([17dbf6e](https://github.com/react-native-video/react-native-video/commit/17dbf6e8264c5c6bed10ff23d96c2b7296a49651))
|
||||
* implement startPosition ([#3355](https://github.com/react-native-video/react-native-video/issues/3355)) ([2648502](https://github.com/react-native-video/react-native-video/commit/2648502b364c2802f5a2a7302c31200905c0a807))
|
||||
|
||||
# [6.0.0-beta.1](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.0...v6.0.0-beta.1) (WIP)
|
||||
* **android:** fix leak caused by removing lifecycle listener too early ([#3380](https://github.com/react-native-video/react-native-video/pull/3380))
|
||||
|
||||
# [6.0.0-beta.0](https://github.com/react-native-video/react-native-video/compare/v6.0.0-alpha.11...v6.0.0-beta.0) (2023-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **example:** remove dependency loop ([#3353](https://github.com/react-native-video/react-native-video/issues/3353)) ([211c3c7](https://github.com/react-native-video/react-native-video/commit/211c3c7d08c8438bfca3350f0070cfec0ae5bc56))
|
||||
* **ios:** change isPlaybackLikelyToKeepUp check ([#3357](https://github.com/react-native-video/react-native-video/issues/3357)) ([1ba93f9](https://github.com/react-native-video/react-native-video/commit/1ba93f9e9d33f653f0e01214f220e1e5eda819f5))
|
||||
* **ios:** fix cache playerItemPrepareText type ([#3358](https://github.com/react-native-video/react-native-video/issues/3358)) ([0e23952](https://github.com/react-native-video/react-native-video/commit/0e23952cea5c71324a2f5eea0383c4db9e02504b))
|
||||
* **ios:** fix external text tracks crashes with m3u8 files ([#3330](https://github.com/react-native-video/react-native-video/issues/3330)) ([782e7e0](https://github.com/react-native-video/react-native-video/commit/782e7e0df1386ef0aad3f00d73171d04d6cf725d))
|
||||
* update onError definition to match implementation ([#3349](https://github.com/react-native-video/react-native-video/issues/3349)) ([fdbd6a6](https://github.com/react-native-video/react-native-video/commit/fdbd6a6ba8aef2da854ff7b0fbf25085ce6983e3))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **android:** replace deprecated ExoPlayer2 with AndroidX media3 ([#3337](https://github.com/react-native-video/react-native-video/issues/3337)) ([f2e80e9](https://github.com/react-native-video/react-native-video/commit/f2e80e9f2d1acc97080d48913802639dd2f38346))
|
||||
|
||||
# [6.0.0-alpha.11](https://github.com/react-native-video/react-native-video/compare/v6.0.0-alpha.10...v6.0.0-alpha.11) (2023-11-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix bad package release process ([#3347](https://github.com/react-native-video/react-native-video/issues/3347)) ([f961f95](https://github.com/react-native-video/react-native-video/commit/f961f952a483192ee3de1f7bae59419ec6ddc5b7))
|
||||
|
||||
# [6.0.0-alpha.10](https://github.com/react-native-video/react-native-video/compare/v6.0.0-alpha.9...v6.0.0-alpha.10) (2023-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes where Android's muted prop behavior differs from iOS ([#3339](https://github.com/react-native-video/react-native-video/issues/3339)) ([8fbdc28](https://github.com/react-native-video/react-native-video/commit/8fbdc28a73a0b3ffd3691ef0c8cf523c760ae288))
|
||||
* **ios:** fix wrong fullscreen method definition ([#3338](https://github.com/react-native-video/react-native-video/issues/3338)) ([7f49b56](https://github.com/react-native-video/react-native-video/commit/7f49b560278262fb4276f931404c70672a6445c8))
|
||||
* **ios:** player is frozen after re-focusing on the app ([#3326](https://github.com/react-native-video/react-native-video/issues/3326)) ([722ae34](https://github.com/react-native-video/react-native-video/commit/722ae3477a68aecb812b26d71ea22a17dda71f50))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `onVolumeChange` event ([#3322](https://github.com/react-native-video/react-native-video/issues/3322)) ([cdbc856](https://github.com/react-native-video/react-native-video/commit/cdbc85638789da0002cdadb13190963d4c1332c2))
|
||||
* add release-it ([#3342](https://github.com/react-native-video/react-native-video/issues/3342)) ([da27089](https://github.com/react-native-video/react-native-video/commit/da270891fbce485bb132825a336638f2af98408d))
|
||||
* **ios:** add onBandwidthUpdate event ([#3331](https://github.com/react-native-video/react-native-video/issues/3331)) ([9054db3](https://github.com/react-native-video/react-native-video/commit/9054db35d7d5e4e6d54739fc9349576c03522d7c))
|
||||
|
||||
## Changelog
|
||||
|
||||
## Next
|
||||
- Android, iOS: add onVolumeChange event #3322
|
||||
- iOS: Externally loaded text tracks not loading properly [#3461](https://github.com/react-native-video/react-native-video/pull/3461)
|
||||
|
||||
### Version 6.0.0-alpha.9
|
||||
- All: add built-in typescript support [#3266](https://github.com/react-native-video/react-native-video/pull/3266)
|
||||
- All: update documentation generation [#3296](https://github.com/react-native-video/react-native-video/pull/3296)
|
||||
- **BREAKING CHANGE**❗️Android: update isCodecSupported to return enum [#3254](https://github.com/react-native-video/react-native-video/pull/3254)
|
||||
- Android: use explicit not-exported flag for AudioBecomingNoisyReceiver [#3327](https://github.com/react-native-video/react-native-video/pull/3327)
|
||||
- Android: remove kotlin-android-extensions [#3299](https://github.com/react-native-video/react-native-video/pull/3299)
|
||||
- Android: ensure audio volume is changed in UI thread [3292](https://github.com/react-native-video/react-native-video/pull/3292)
|
||||
- Android: multiple internal refactor and switch to kotlin
|
||||
- Android: refactor log management and add an option to increase log verbosity [#3277](https://github.com/react-native-video/react-native-video/pull/3277)
|
||||
- iOS: Fix audio session category when not using the audioOutput prop
|
||||
- iOS: implement onPlaybackStateChanged callback [#3307](https://github.com/react-native-video/react-native-video/pull/3307)
|
||||
- iOS: remove false calls at onPlaybackRateChange [#3306](https://github.com/react-native-video/react-native-video/pull/3306)
|
||||
- iOS: audio does not work with headphones [#3284](https://github.com/react-native-video/react-native-video/pull/3284)
|
||||
- iOS: Resuming video ad after closing the in-app browser on iOS [#3275](https://github.com/react-native-video/react-native-video/pull/3275)
|
||||
- iOS, Android: expose playback functions to ref [#3245](https://github.com/react-native-video/react-native-video/pull/3245)
|
||||
- tvOS: fix build: [#3276](https://github.com/react-native-video/react-native-video/pull/3276)
|
||||
- Windows: fix build error from over-specified SDK version [#3246](https://github.com/react-native-video/react-native-video/pull/3246)
|
||||
- Windows: fix `onError` not being raised [#3247](https://github.com/react-native-video/react-native-video/pull/3247)
|
||||
|
||||
### Version 6.0.0-alpha.8
|
||||
- All: Playing audio over earpiece [#2887](https://github.com/react-native-video/react-native-video/issues/2887)
|
||||
- All: Prepare for fabric [#3175](https://github.com/react-native-video/react-native-video/pull/3175) [#]()
|
||||
- iOS: Fix Pip [#3221](https://github.com/react-native-video/react-native-video/pull/3221)
|
||||
- iOS: Fix regression in presentFullscreenPlayer & dismissFullscreenPlayer [#3230](https://github.com/react-native-video/react-native-video/pull/3230)
|
||||
- tvOS: Fix build [#3207](https://github.com/react-native-video/react-native-video/pull/3207)
|
||||
- tvOS: Add sample [#3208](https://github.com/react-native-video/react-native-video/pull/3208)
|
||||
- tvOS: Allow chapter customization [#3216](https://github.com/react-native-video/react-native-video/pull/3216)
|
||||
- doc: Fix internal links [#3229](https://github.com/react-native-video/react-native-video/pull/3229)
|
||||
|
||||
### Version 6.0.0-alpha.7
|
||||
- All: clean JS warnings (https://github.com/react-native-video/react-native-video/pull/3183)
|
||||
- Android: Add shutterView color configurtion (https://github.com/react-native-video/react-native-video/pull/3179)
|
||||
- Android: React native 0.73 support (https://github.com/react-native-video/react-native-video/pull/3163)
|
||||
- Android: Fix memory leaks from AudioManager [#3123](https://github.com/react-native-video/react-native-video/pull/3123)
|
||||
- Android: Fixed syntax error [#3182](https://github.com/react-native-video/react-native-video/issues/3182)
|
||||
- iOS: Fix freeze at playback startup (https://github.com/react-native-video/react-native-video/pull/3173)
|
||||
- iOS: Various safety checks (https://github.com/react-native-video/react-native-video/pull/3168)
|
||||
|
||||
### Version 6.0.0-alpha.6
|
||||
- Feature: Video range support [#3030](https://github.com/react-native-video/react-native-video/pull/3030)
|
||||
- iOS: remove undocumented `currentTime` property [#3064](https://github.com/react-native-video/react-native-video/pull/3064)
|
||||
- iOS: make sure that the audio in ads is muted when the player is muted. [#3068](https://github.com/react-native-video/react-native-video/pull/3077)
|
||||
- iOS: make IMA build optionnal
|
||||
|
||||
### Version 6.0.0-alpha.5
|
||||
|
||||
- iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017)
|
||||
- iOS: app crashes on call to presentFullScreenPlayer [#2808](https://github.com/react-native-video/react-native-video/pull/2971)
|
||||
- Android: Fix publicated progress handler causing duplicated progress event [#2972](https://github.com/react-native-video/react-native-video/pull/2972)
|
||||
- Android: Fix audio/Subtitle tracks selection [#2979](https://github.com/react-native-video/react-native-video/pull/2979)
|
||||
- Android: add new events on tracks changed to be notified of audio/text/video Tracks update during playback [2806](https://github.com/react-native-video/react-native-video/pull/2806)
|
||||
- Feature: Add VAST support for AVOD [#2923](https://github.com/react-native-video/react-native-video/pull/2923)
|
||||
- Sample: Upgrade react-native version of basic sample [#2960](https://github.com/react-native-video/react-native-video/pull/2960)
|
||||
|
||||
### Version 6.0.0-alpha.4
|
||||
|
||||
- ensure src is always provided to native player even if it is invalid [#2857](https://github.com/react-native-video/react-native-video/pull/2857)
|
||||
- Sample: Add react-native-video controls support [#2852](https://github.com/react-native-video/react-native-video/pull/2852)
|
||||
- Android: Switch Google's maven repository to default `google()` [#2860](https://github.com/react-native-video/react-native-video/pull/2860)
|
||||
- Android: Implement focusable prop so the video view can toggle whether it is focusable for non-touch devices [#2819](https://github.com/react-native-video/react-native-video/issues/2819)
|
||||
- Android: fix linter warning [#2891] (https://github.com/react-native-video/react-native-video/pull/2891)
|
||||
- Fix iOS RCTSwiftLog naming collision [#2868](https://github.com/react-native-video/react-native-video/issues/2868)
|
||||
- Added "homepage" to package.json [#2882](https://github.com/react-native-video/react-native-video/pull/2882)
|
||||
- Fix regression when fullscreen prop is used combined with controls [#2911](https://github.com/react-native-video/react-native-video/pull/2911)
|
||||
- Fix: memory leak issue on iOS [#2907](https://github.com/react-native-video/react-native-video/pull/2907)
|
||||
- Fix setting text tracks before player is initialized on iOS [#2935](https://github.com/react-native-video/react-native-video/pull/2935)
|
||||
|
||||
### Version 6.0.0-alpha.3
|
||||
|
||||
- Fix ios build [#2854](https://github.com/react-native-video/react-native-video/pull/2854)
|
||||
|
||||
### Version 6.0.0-alpha.2
|
||||
|
||||
- Upgrade ExoPlayer to 2.18.1 [#2846](https://github.com/react-native-video/react-native-video/pull/2846)
|
||||
- Feature add new APIs to query supported features of device decoder (widevine level & codec capabilities) on android [#2740](https://github.com/react-native-video/react-native-video/pull/2740)
|
||||
- Feature add support of subtitle styling on android [#2759](https://github.com/react-native-video/react-native-video/pull/2759)
|
||||
- Fix Android #2690 ensure onEnd is not sent twice [#2690](https://github.com/react-native-video/react-native-video/issues/2690)
|
||||
- Fix Exoplayer progress not reported when paused [#2664](https://github.com/react-native-video/react-native-video/pull/2664)
|
||||
- Call playbackRateChange onPlay and onPause [#1493](https://github.com/react-native-video/react-native-video/pull/1493)
|
||||
- Fix being unable to disable sideloaded texttracks in the AVPlayer [#2679](https://github.com/react-native-video/react-native-video/pull/2679)
|
||||
- Fixed crash when iOS seek method called reject on the promise [#2743](https://github.com/react-native-video/react-native-video/pull/2743)
|
||||
- Fix maxBitRate property being ignored on Android [#2670](https://github.com/react-native-video/react-native-video/pull/2670)
|
||||
- Fix crash when the source is a cameraroll [#2639] (https://github.com/react-native-video/react-native-video/pull/2639)
|
||||
- Fix IOS UI frame drop on loading video [#2848] (https://github.com/react-native-video/react-native-video/pull/2848)
|
||||
|
||||
### Version 6.0.0-alpha.1
|
||||
|
||||
- Remove Android MediaPlayer support [#2724](https://github.com/react-native-video/react-native-video/pull/2724)
|
||||
**WARNING**: when switching from older version to V6, you need to remove all refrerences of android-exoplayer. This android-exoplayer folder has been renamed to android. Exoplayer is now the only player implementation supported.
|
||||
|
||||
- Replace Image.propTypes with ImagePropTypes. [#2718](https://github.com/react-native-video/react-native-video/pull/2718)
|
||||
- Fix iOS build caused by type mismatch [#2720](https://github.com/react-native-video/react-native-video/pull/2720)
|
||||
- ERROR TypeError: undefined is not an object (evaluating '_reactNative.Image.propTypes.resizeMode') [#2714](https://github.com/react-native-video/react-native-video/pull/2714)
|
||||
- Fix video endless loop when repeat set to false or not specified. [#2329](https://github.com/react-native-video/react-native-video/pull/2329)
|
||||
|
||||
### Version 6.0.0-alpha.0
|
||||
|
||||
- Support disabling buffering [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Fix AudioFocus bug that could cause the player to stop responding to play/pause in some instances. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Fix player crashing when it is being cleared. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Add support for customising back buffer duration and handle network errors gracefully to prevent releasing the player when network is lost. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Allow player to be init before source is provided, and later update once a source is provided. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Adds handling for providing a empty source in order to stop playback and clear out any existing content [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Add support for detecting if format is supported and exclude unsupported resolutions from auto quality selection and video track info in RN. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Improve error handling [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Add support for L1 to L3 Widevine fallback if playback fails initially. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Reduce buffer size based on available heap [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Force garbage collection when there is no available memory [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Improve memory usage [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Support disabling screen recording [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Improved error capturing [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Fix DRM init crashes [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Improve progress reporting [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Fix progress loss when network connection is regained [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
|
||||
- Add Google's maven repository to avoid build error [#2552](https://github.com/react-native-video/react-native-video/pull/2552)
|
||||
- Fix iOS 15.4 HLS playback race condition [#2633](https://github.com/react-native-video/react-native-video/pull/2633)
|
||||
- Fix app crash from NPE in Exoplayer error handler [#2575](https://github.com/react-native-video/react-native-video/pull/2575)
|
||||
- Fix default closed captioning behavior for Android ExoPlayer [#2181](https://github.com/react-native-video/react-native-video/pull/2181)
|
||||
- Disable pipController init if pictureInPicture is false [#2645](https://github.com/react-native-video/react-native-video/pull/2645)
|
||||
- Make sure modifiers are applied before playing [#2395](https://github.com/react-native-video/react-native-video/pull/2395)
|
||||
- Better support newer versions of RNW (64 and newer) [#2535](https://github.com/react-native-video/react-native-video/pull/2535)
|
||||
- Fix nil string uri parameter error [#695](https://github.com/react-native-video/react-native-video/pull/695)
|
||||
- (Breaking) Bump shaka-player to 3.3.2 [#2587](https://github.com/react-native-video/react-native-video/pull/2587)
|
||||
- Improve basic player example on android [#2662](https://github.com/react-native-video/react-native-video/pull/2662)
|
||||
- Ensure we always use `hideShutterView` before showing the `shutterView` on Android [#2609](https://github.com/react-native-video/react-native-video/pull/2609)
|
||||
- Convert iOS implementation to Swift [#2527](https://github.com/react-native-video/react-native-video/pull/2527)
|
||||
- Add iOS support for decoding offline sources [#2527](https://github.com/react-native-video/react-native-video/pull/2527)
|
||||
- Update basic example applications (React Native 0.63.4) [#2527](https://github.com/react-native-video/react-native-video/pull/2527)
|
||||
- Upgrade ExoPlayer to 2.17.1 [#2498](https://github.com/react-native-video/react-native-video/pull/2498)
|
||||
- Fix volume reset issue in exoPlayer [#2371](https://github.com/react-native-video/react-native-video/pull/2371)
|
||||
- Change WindowsTargetPlatformVersion to 10.0 [#2706](https://github.com/react-native-video/react-native-video/pull/2706)
|
||||
- Fixed Android seeking bug [#2712](https://github.com/react-native-video/react-native-video/pull/2712)
|
||||
- Fixed `onReadyForDisplay` not being called [#2721](https://github.com/react-native-video/react-native-video/pull/2721)
|
||||
- Fix type of `_eventDispatcher` on iOS target to match `bridge.eventDispatcher()` [#2720](https://github.com/react-native-video/react-native-video/pull/2720)
|
||||
|
||||
### Version 5.2.0
|
||||
|
||||
- Fix for tvOS native audio menu language selector
|
||||
- Update ExoPlayer to allow pre-init and content clear [#2412] (https://github.com/react-native-video/react-native-video/pull/2412)
|
||||
- iOS rate is reset to 1.0 after play/pause [#2167] (https://github.com/react-native-video/react-native-video/pull/2167)
|
||||
- Upgrade ExoPlayer to 2.13.2 [#2317] (https://github.com/react-native-video/react-native-video/pull/2317)
|
||||
- Fix AudioFocus pausing video when attempting to play [#2311] (https://github.com/react-native-video/react-native-video/pull/2311)
|
||||
|
||||
### Version 5.1.0-alpha9
|
||||
|
||||
- Add ARM64 support for windows [#2137](https://github.com/react-native-community/react-native-video/pull/2137)
|
||||
- Fix deprecated API bug for windows [#2119](https://github.com/react-native-video/react-native-video/pull/2119)
|
||||
- Added `rate` property and autolinking support for windows [#2206](https://github.com/react-native-video/react-native-video/pull/2206)
|
||||
|
||||
### Version 5.1.0-alpha8
|
||||
|
||||
- Fixing ID3 Frame Error When Receiving EventMessage in TimedMetadata [#2116](https://github.com/react-native-community/react-native-video/pull/2116)
|
||||
|
||||
### Version 5.1.0-alpha7
|
||||
|
||||
- Basic support for DRM on iOS and Android [#1445](https://github.com/react-native-community/react-native-video/pull/1445)
|
||||
|
||||
### Version 5.1.0-alpha6
|
||||
|
||||
- Fix iOS bug which would break size of views when video is displayed with controls on a non full-screen React view. [#1931](https://github.com/react-native-community/react-native-video/pull/1931)
|
||||
- Fix video dimensions being undefined when playing HLS in ios. [#1992](https://github.com/react-native-community/react-native-video/pull/1992)
|
||||
- Add support for audio mix with other apps for iOS. [#1978](https://github.com/react-native-community/react-native-video/pull/1978)
|
||||
- Properly implement pending seek for iOS. [#1994](https://github.com/react-native-community/react-native-video/pull/1994)
|
||||
- Added `preferredForwardBufferDuration` (iOS) - the duration the player should buffer media from the network ahead of the playhead to guard against playback disruption. (#1944)
|
||||
- Added `currentPlaybackTime` (Android ExoPlayer, iOS) - when playing an HLS live stream with a `EXT-X-PROGRAM-DATE-TIME` tag configured, then this property will contain the epoch value in msec. (#1944)
|
||||
- Added `trackId` (Android ExoPlayer) - Configure an identifier for the video stream to link the playback context to the events emitted. (#1944)
|
||||
- Added preventsDisplaySleepDuringVideoPlayback (#2019)
|
||||
- Reverted the JS fullscreening for Android. [#2013](https://github.com/react-native-community/react-native-video/pull/2013)
|
||||
- Set iOS request headers without needing to edit RCTVideo.m. [#2014](https://github.com/react-native-community/react-native-video/pull/2014)
|
||||
- Fix exoplayer aspect ratio update on source changes [#2053](https://github.com/react-native-community/react-native-video/pull/2053)
|
||||
|
||||
### Version 5.1.0-alpha5
|
||||
|
||||
- Add support for react-native Windows Cpp/WinRT [#1893]((https://github.com/react-native-community/react-native-video/pull/1893))
|
||||
|
||||
### Version 5.1.0-alpha4
|
||||
|
||||
- Fix android play/pause bug related to full-screen mode [#1916](https://github.com/react-native-community/react-native-video/pull/1916)
|
||||
|
||||
### Version 5.1.0-alpha3
|
||||
|
||||
- Improve Android Audio Focus [#1897](https://github.com/react-native-community/react-native-video/pull/1897)
|
||||
|
||||
### Version 5.1.0-alpha2
|
||||
|
||||
- Added support for full-screen functionality in Android Exoplayer [#1730](https://github.com/react-native-community/react-native-video/pull/1730)
|
||||
|
||||
### Version 5.1.0-alpha1
|
||||
|
||||
- Fixed Exoplayer doesn't work with mute=true (Android). [#1696](https://github.com/react-native-community/react-native-video/pull/1696)
|
||||
- Added support for automaticallyWaitsToMinimizeStalling property (iOS) [#1723](https://github.com/react-native-community/react-native-video/pull/1723)
|
||||
- Bump Exoplayer to 2.10.4, remove deprecated usages of Exoplayer methods (Android). [#1753](https://github.com/react-native-community/react-native-video/pull/1753)
|
||||
- Preserve Exoplayer BandwidthMeter instance across video plays, this should noticeably improve streaming bandwidth detection (Android).
|
||||
|
||||
### Version 5.0.2
|
||||
|
||||
- Fix crash when RCTVideo's superclass doesn't observe the keyPath 'frame' (iOS) [#1720](https://github.com/react-native-community/react-native-video/pull/1720)
|
||||
|
||||
### Version 5.0.1
|
||||
|
||||
- Fix AndroidX Support bad merge
|
||||
|
||||
### Version 5.0.0 [Deprecated]
|
||||
|
||||
- AndroidX Support
|
||||
|
||||
### Version 4.4.4
|
||||
|
||||
- Handle racing conditions when props are settled on Exoplayer
|
||||
|
||||
### Version 4.4.3
|
||||
|
||||
- Fix mute/unmute when controls are present (iOS) [#1654](https://github.com/react-native-community/react-native-video/pull/1654)
|
||||
- Fix Android videos being able to play with background music/audio from other apps.
|
||||
- Fixed memory leak on iOS when using `controls` [#1647](https://github.com/react-native-community/react-native-video/pull/1647)
|
||||
- (Android) Update gradle and target SDK [#1629](https://github.com/react-native-community/react-native-video/pull/1629)
|
||||
- Fix iOS stressed mount/unmount crash [#1646](https://github.com/react-native-community/react-native-video/pull/1646)
|
||||
|
||||
### Version 4.4.2
|
||||
|
||||
- Change compileOnly to implementation on gradle (for newer gradle versions and react-native 0.59 support) [#1592](https://github.com/react-native-community/react-native-video/pull/1592)
|
||||
- Replaced RCTBubblingEventBlock events by RCTDirectEventBlock to avoid event name collisions [#1625](https://github.com/react-native-community/react-native-video/pull/1625)
|
||||
- Added `onPlaybackRateChange` to README [#1578](https://github.com/react-native-community/react-native-video/pull/1578)
|
||||
- Added `onReadyForDisplay` to README [#1627](https://github.com/react-native-community/react-native-video/pull/1627)
|
||||
- Improved handling of poster image. Fixes bug with displaying video and poster simultaneously. [#1627](https://github.com/react-native-community/react-native-video/pull/1627)
|
||||
- Fix background audio stopping on iOS when using `controls` [#1614](https://github.com/react-native-community/react-native-video/pull/1614)
|
||||
|
||||
### Version 4.4.1
|
||||
|
||||
- Fix tvOS picture-in-picture compilation regression [#1518](https://github.com/react-native-community/react-native-video/pull/1518)
|
||||
- fullscreen rotation issues with iOS built-in controls [#1441](https://github.com/react-native-community/react-native-video/pull/1441)
|
||||
- Fix player freeze when playing audio files on ExoPlayer [#1529](https://github.com/react-native-community/react-native-video/pull/1529)
|
||||
|
||||
### Version 4.4.0
|
||||
|
||||
- Fix runtime warning by replacing `UIManager.RCTVideo` with `UIManager.getViewManagerConfig('RCTVideo')` (and ensuring backwards compat) [#1487](https://github.com/react-native-community/react-native-video/pull/1487)
|
||||
- Fix loading package resolved videos when using video-caching [#1438](https://github.com/react-native-community/react-native-video/pull/1438)
|
||||
- Fix "message sent to deallocated instance" crash on ios [#1482](https://github.com/react-native-community/react-native-video/pull/1482)
|
||||
- Display a warning when source is empty [#1478](https://github.com/react-native-community/react-native-video/pull/1478)
|
||||
- Don't crash on iOS for an empty source [#1246](https://github.com/react-native-community/react-native-video/pull/1246)
|
||||
- Recover from from transient internet failures when loading on ExoPlayer [#1448](https://github.com/react-native-community/react-native-video/pull/1448)
|
||||
- Add controls support for ExoPlayer [#1414](https://github.com/react-native-community/react-native-video/pull/1414)
|
||||
- Fix check for text tracks when iOS caching enabled [#1387](https://github.com/react-native-community/react-native-video/pull/1387)
|
||||
- Add support for Picture in Picture on iOS [#1325](https://github.com/react-native-community/react-native-video/pull/1325)
|
||||
- Fix UIManager undefined variable [#1488](https://github.com/react-native-community/react-native-video/pull/1488)
|
||||
|
||||
### Version 4.3.0
|
||||
|
||||
- Fix iOS video not displaying after switching source [#1395](https://github.com/react-native-community/react-native-video/pull/1395)
|
||||
- Add the filterEnabled flag, fixes iOS video start time regression [#1384](https://github.com/react-native-community/react-native-video/pull/1384)
|
||||
- Fix text not appearing in release builds of Android apps [#1373](https://github.com/react-native-community/react-native-video/pull/1373)
|
||||
- Update to ExoPlayer 2.9.3 [#1406](https://github.com/react-native-community/react-native-video/pull/1406)
|
||||
- Add video track selection & onBandwidthUpdate [#1199](https://github.com/react-native-community/react-native-video/pull/1199)
|
||||
- Recovery from transient internet failures and props to configure the custom retry count [#1448](https://github.com/react-native-community/react-native-video/pull/1448)
|
||||
|
||||
### Version 4.2.0
|
||||
|
||||
- Don't initialize filters on iOS unless a filter is set. This was causing a startup performance regression [#1360](https://github.com/react-native-community/react-native-video/pull/1360)
|
||||
- Support setting the maxBitRate [#1310](https://github.com/react-native-community/react-native-video/pull/1310)
|
||||
- Fix useTextureView not defaulting to true [#1383](https://github.com/react-native-community/react-native-video/pull/1383)
|
||||
- Fix crash on MediaPlayer w/ Android 4.4 & avoid memory leak [#1328](https://github.com/react-native-community/react-native-video/pull/1328)
|
||||
|
||||
### Version 4.1.0
|
||||
|
||||
- Generate onSeek on Android ExoPlayer & MediaPlayer after seek completes [#1351](https://github.com/react-native-community/react-native-video/pull/1351)
|
||||
- Remove unneeded onVideoSaved event [#1350](https://github.com/react-native-community/react-native-video/pull/1350)
|
||||
- Disable AirPlay if sidecar text tracks are enabled [#1304](https://github.com/react-native-community/react-native-video/pull/1304)
|
||||
- Add possibility to remove black screen while video is loading in Exoplayer [#1355](https://github.com/react-native-community/react-native-video/pull/1355)
|
||||
|
||||
### Version 4.0.1
|
||||
|
||||
- Add missing files to package.json [#1342](https://github.com/react-native-community/react-native-video/pull/1342)
|
||||
|
||||
### Version 4.0.0
|
||||
|
||||
- Partial support for timed metadata on Android MediaPlayer [#707](https://github.com/react-native-community/react-native-video/pull/707)
|
||||
- Support video caching for iOS [#955](https://github.com/react-native-community/react-native-video/pull/955)
|
||||
- Video caching cleanups [#1172](https://github.com/react-native-community/react-native-video/pull/1172)
|
||||
- Add ipod-library support [#926](https://github.com/react-native-community/react-native-video/pull/926/files)
|
||||
- Fix crash on ExoPlayer when there are no audio tracks [#1233](https://github.com/react-native-community/react-native-video/pull/1233)
|
||||
- Reduce package size [#1231](https://github.com/react-native-community/react-native-video/pull/1231)
|
||||
- Remove unnecessary import in TextTrackType [#1229](https://github.com/react-native-community/react-native-video/pull/1229)
|
||||
- Prevent flash between poster and video [#1167](https://github.com/react-native-community/react-native-video/pull/1167)
|
||||
- Support react-native-dom [#1253](https://github.com/react-native-community/react-native-video/pull/1253)
|
||||
- Update to ExoPlayer 2.8.2. Android SDK 26 now required [#1170](https://github.com/react-native-community/react-native-video/pull/1170)
|
||||
- Update to ExoPlayer 2.8.4 [#1266](https://github.com/react-native-community/react-native-video/pull/1266)
|
||||
- Add fullscreenOrientation option for iOS [#1215](https://github.com/react-native-community/react-native-video/pull/1215)
|
||||
- Update to ExoPlayer 2.9.0 [#1285](https://github.com/react-native-community/react-native-video/pull/1285)
|
||||
- Switch useTextureView to default to `true` [#1286](https://github.com/react-native-community/react-native-video/pull/1286)
|
||||
- Re-add fullscreenAutorotate prop [#1303](https://github.com/react-native-community/react-native-video/pull/1303)
|
||||
- Make seek throw a useful error for NaN values [#1283](https://github.com/react-native-community/react-native-video/pull/1283)
|
||||
- Video Filters and Save Video [#1306](https://github.com/react-native-community/react-native-video/pull/1306)
|
||||
- Fix: volume should not change on onAudioFocusChange event [#1327](https://github.com/react-native-community/react-native-video/pull/1327)
|
||||
- Update ExoPlayer to 2.9.1 and OkHTTP to 3.12.0 [#1338](https://github.com/react-native-community/react-native-video/pull/1338)
|
||||
|
||||
### Version 3.2.0
|
||||
|
||||
- Basic fullscreen support for Android MediaPlayer [#1138](https://github.com/react-native-community/react-native-video/pull/1138)
|
||||
- Simplify default Android SDK code [#1145](https://github.com/react-native-community/react-native-video/pull/1145) [#1146](https://github.com/react-native-community/react-native-video/pull/1146)
|
||||
- Various iOS sideloaded text track fixes [#1157](https://github.com/react-native-community/react-native-video/pull/1157)
|
||||
- Fix #1150 where assets with bundled assets don't work on iOS in release mode [#1162](https://github.com/react-native-community/react-native-video/pull/1162)
|
||||
- Support configuring the buffer on Android ExoPlayer [#1160](https://github.com/react-native-community/react-native-video/pull/1160)
|
||||
- Prevent sleep from sleeping while videos are playing on Android MediaPlayer [#1117](https://github.com/react-native-community/react-native-video/pull/1117)
|
||||
- Update NewtonSoft JSON to match react-native-windows version [#1169](https://github.com/react-native-community/react-native-video/pull/1169)
|
||||
|
||||
### Version 3.1.0
|
||||
|
||||
- Support sidecar text tracks on iOS [#1109](https://github.com/react-native-community/react-native-video/pull/1109)
|
||||
- Support onAudioBecomingNoisy on iOS [#1131](https://github.com/react-native-community/react-native-video/pull/1131)
|
||||
|
||||
### Version 3.0
|
||||
|
||||
- Inherit Android buildtools and SDK version from the root project [#1081](https://github.com/react-native-community/react-native-video/pull/1081)
|
||||
- Automatically play on ExoPlayer when the paused prop is not set [#1083](https://github.com/react-native-community/react-native-video/pull/1083)
|
||||
- Preserve Android MediaPlayer paused prop when backgrounding [#1082](https://github.com/react-native-community/react-native-video/pull/1082)
|
||||
- Support specifying headers on ExoPlayer as part of the source [#805](https://github.com/react-native-community/react-native-video/pull/805)
|
||||
- Prevent iOS onLoad event during seeking [#1088](https://github.com/react-native-community/react-native-video/pull/1088)
|
||||
- ExoPlayer playableDuration incorrect [#1089](https://github.com/react-native-community/react-native-video/pull/1089)
|
||||
|
||||
### Version 2.3.1
|
||||
|
||||
- Revert PR to inherit Android SDK versions from root project. Re-add in 3.0 [#1080](https://github.com/react-native-community/react-native-video/pull/1080)
|
||||
|
||||
### Version 2.3.0
|
||||
|
||||
- Support allowsExternalPlayback on iOS [#1057](https://github.com/react-native-community/react-native-video/pull/1057)
|
||||
- Inherit Android buildtools and SDK version from the root project [#999](https://github.com/react-native-community/react-native-video/pull/999)
|
||||
- Fix bug that caused ExoPlayer to start paused if playInBackground was set [#833](https://github.com/react-native-community/react-native-video/pull/833)
|
||||
- Fix crash if clearing an observer on iOS that was already cleared [#1075](https://github.com/react-native-community/react-native-video/pull/1075)
|
||||
- Add audioOnly prop for music files [#1039](https://github.com/react-native-community/react-native-video/pull/1039)
|
||||
- Support seeking with more exact tolerance on iOS [#1076](https://github.com/react-native-community/react-native-video/pull/1076)
|
||||
|
||||
### Version 2.2.0
|
||||
|
||||
- Text track selection support for iOS & ExoPlayer [#1049](https://github.com/react-native-community/react-native-video/pull/1049)
|
||||
- Support outputting to a TextureView on Android ExoPlayer [#1058](https://github.com/react-native-community/react-native-video/pull/1058)
|
||||
- Support changing the left/right balance on Android MediaPlayer [#1051](https://github.com/react-native-community/react-native-video/pull/1051)
|
||||
- Prevent multiple onEnd notifications on iOS [#832](https://github.com/react-native-community/react-native-video/pull/832)
|
||||
- Fix doing a partial swipe on iOS causing a black screen [#1048](https://github.com/react-native-community/react-native-video/pull/1048)
|
||||
- Fix crash when switching to a new source on iOS [#974](https://github.com/react-native-community/react-native-video/pull/974)
|
||||
- Add cookie support for ExoPlayer [#922](https://github.com/react-native-community/react-native-video/pull/922)
|
||||
- Remove ExoPlayer onMetadata that wasn't being used [#1040](https://github.com/react-native-community/react-native-video/pull/1040)
|
||||
- Fix bug where setting the progress interval on iOS didn't work [#800](https://github.com/react-native-community/react-native-video/pull/800)
|
||||
- Support setting the poster resize mode [#595](https://github.com/react-native-community/react-native-video/pull/595)
|
||||
133
CODE_OF_CONDUCT.md
Normal file
133
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[INSERT CONTACT METHOD].
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
128
CONTRIBUTING.md
128
CONTRIBUTING.md
@@ -1,31 +1,119 @@
|
||||
## Issues
|
||||
# Contributing
|
||||
|
||||
* New issues are reviewed and if they require additional work will be marked with the [`triage needed`](https://github.com/react-native-video/react-native-video/labels/triage%20needed) label. This is an open call for help from the community to verify the issue and help categorize it. If an issue stays in this state for a long time, it will be closed unresolved.
|
||||
* Once an issue has been reviewed it will be labeled with [`help wanted`](https://github.com/react-native-video/react-native-video/labels/help%20wanted) to indicate it is ready to be worked on. Please wait for this label before submitting a PR to avoid spending time on something that is likely to be rejected.
|
||||
Contributions are always welcome, no matter how large or small!
|
||||
|
||||
## Cleanup
|
||||
We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
|
||||
|
||||
* Given the history of this project, we are going to be more aggressive than usual in keeping things clean. We are working with limited resources and do not want to return to the 1000+ open issues state. This is not meant to be disrespectful or hostile. It is just a way to keep the limited resources we have focused. If your issue was closed prematurely, just chime in and engage!
|
||||
* Issues and pull requests that become stale (60 days of inactivity) will be closed unless assigned and show progress.
|
||||
* If the issue creator fails to provide additional information within a week when asked, we may close the issue to keep things tidy (but you can always comment back and we can reopen).
|
||||
## Development workflow
|
||||
|
||||
## Pull Requests
|
||||
This project is a monorepo managed using [Bun workspaces](https://bun.sh/docs/install/workspaces). It contains the following packages:
|
||||
|
||||
* Please open an issue before opening a PR to make sure the proposed change is wanted and is likely to be merged. We don't want you to waste your time!
|
||||
* Pull requests require 1-3 approved reviews to be merged.
|
||||
* The number of reviews depends on the complexity by adding up (max of 3):
|
||||
* `1` reviewer for each PR
|
||||
* `1` if more than 3 files and/or 30 lines of code changed
|
||||
* `1` for each native platform code changes involved
|
||||
- An Library package in the `packages/react-native-video` directory.
|
||||
- An example app in the `example/` directory.
|
||||
|
||||
For example, a single file JS code change requires 1 review while a 3 files iOS code change requires 3 reviews. As soon as the reviews show up as approved without any requested changes, the PR will be merged into the next milestone.
|
||||
To get started with the project, run `bun install` in the root directory to install the required dependencies for each package:
|
||||
|
||||
* Reviewers will be asked to assign a risk level when they are done from 1 (super safe) to 5 (super risky). A release with any risk level 4 or 5 will be published as a major version, otherwise as a patch or minor based on the changes. Prepare for some large version increments while we get more comfortable... (but remember versions are free).
|
||||
```sh
|
||||
bun install
|
||||
```
|
||||
|
||||
* If you have time to help out, look for the [`review requested`](https://github.com/react-native-video/react-native-video/labels/review%20requested) label. It will have another numeric label with it (`1`, `2`, or `3` indicating how many more reviews are needed to merge).
|
||||
> Since the project relies on Bun workspaces, you cannot use [`npm`](https://github.com/npm/cli) or [`yarn`](https://yarnpkg.com/) for development.
|
||||
|
||||
## Releases
|
||||
The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make.
|
||||
|
||||
* Aim for a bi-weekly (every other week) release to flush out whatever was approved and merge. Most people use this with a lock file (and if you don't you are doing it wrong) and should not have any issues with new bugs showing up. This is already a high risk dependency which must be tested well before going into production. Let's take advantage of that and move faster.
|
||||
It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.
|
||||
|
||||
Please do not harass people to review your pull request! You can tag those you feel have relevant experience but please don't abuse this as people will unfollow or mute the project if they are called too many times!
|
||||
If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/VideoExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-video`.
|
||||
|
||||
To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-video` under `Android`.
|
||||
|
||||
You can use various commands from the root directory to work with the project.
|
||||
|
||||
To start the packager:
|
||||
|
||||
```sh
|
||||
bun example start
|
||||
```
|
||||
|
||||
To run the example app on Android:
|
||||
|
||||
```sh
|
||||
bun example android
|
||||
```
|
||||
|
||||
To run the example app on iOS:
|
||||
|
||||
```sh
|
||||
bun example ios
|
||||
```
|
||||
|
||||
Make sure your code passes TypeScript and ESLint. Run the following to verify:
|
||||
|
||||
```sh
|
||||
bun typecheck
|
||||
bun lint
|
||||
```
|
||||
|
||||
To fix formatting errors, run the following:
|
||||
|
||||
```sh
|
||||
bun lint --fix
|
||||
```
|
||||
|
||||
### Commit message convention
|
||||
|
||||
We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
|
||||
|
||||
- `fix`: bug fixes, e.g. fix crash due to deprecated method.
|
||||
- `feat`: new features, e.g. add new method to the module.
|
||||
- `refactor`: code refactor, e.g. migrate from class components to hooks.
|
||||
- `docs`: changes into documentation, e.g. add usage example for the module..
|
||||
- `test`: adding or updating tests, e.g. add integration tests using detox.
|
||||
- `chore`: tooling changes, e.g. change CI config.
|
||||
|
||||
Our pre-commit hooks verify that your commit message matches this format when committing.
|
||||
|
||||
### Linting and tests
|
||||
|
||||
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
|
||||
|
||||
Our pre-commit hooks verify that the linter and tests pass when committing.
|
||||
|
||||
### Publishing to npm
|
||||
|
||||
We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
|
||||
|
||||
To publish new versions, run the following:
|
||||
|
||||
```sh
|
||||
bun release
|
||||
```
|
||||
|
||||
### Scripts
|
||||
|
||||
The `package.json` file contains various scripts for common tasks:
|
||||
|
||||
- `bun install`: setup project by installing dependencies.
|
||||
- `bun typecheck`: type-check files with TypeScript.
|
||||
- `bun lint`: lint files with ESLint.
|
||||
- `bun example start`: start the Metro server for the example app.
|
||||
- `bun example android`: run the example app on Android.
|
||||
- `bun example ios`: run the example app on iOS.
|
||||
|
||||
### Sending a pull request
|
||||
|
||||
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
|
||||
|
||||
When you're sending a pull request:
|
||||
|
||||
- Prefer small pull requests focused on one change.
|
||||
- Verify that linters and tests are passing.
|
||||
- Review the documentation to make sure it looks good.
|
||||
- Follow the pull request template when opening a pull request.
|
||||
- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
|
||||
|
||||
### License
|
||||
|
||||
By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,8 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2022 Project contributors
|
||||
Copyright (c) 2016 Brent Vatne, Baris Sencan
|
||||
|
||||
Copyright (c) 2024-2025 TheWidlarzGroup
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
|
||||
202
README.md
202
README.md
@@ -1,63 +1,163 @@
|
||||
# react-native-video
|
||||
🎬 `<Video>` component for React Native
|
||||
[](https://thewidlarzgroup.com/?utm_source=rnv&utm_medium=readme&utm_id=banner)
|
||||
|
||||
> **Note:** version 5.2.1 won't have any updates. We are currently working on making a 6.0.0 fully stable
|
||||
The most battle-tested open-source video player component for React Native with support for DRM, offline playback, HLS/DASH streaming, and more.
|
||||
|
||||
## Documentation
|
||||
documentation is available at [react-native-video.github.io/react-native-video](https://react-native-video.github.io/react-native-video/)
|
||||
> [!IMPORTANT]
|
||||
> This is a new version (v7) of `react-native-video` that is currently in active development.
|
||||
> You can expect breaking changes and missing features.
|
||||
>
|
||||
> If you have any questions, please contact us at [hi@thewidlarzgroup.com](mailto:hi@thewidlarzgroup.com).
|
||||
|
||||
> if you find some issue with new version, don't hesitate to open a ticket! Also Old version can be found [here](https://github.com/react-native-video/react-native-video/tree/v6.0.0-alpha.8)
|
||||
## 🔍 Features
|
||||
|
||||
## Usage
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| 📱 Plays all video formats natively supported by iOS/Android | ✅ Available & Production Ready by August |
|
||||
| ▶️ Local and remote playback | ✅ Available & Production Ready by August |
|
||||
| 🔁 Streaming: HLS • DASH • SmoothStreaming | ✅ Available & Production Ready by August |
|
||||
| 🧩 Expo plugin support | ✅ Available & Production Ready by August |
|
||||
| 📴 Offline playback, video download, support for side-tracks and side-captions (via [optional SDK](https://docs.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=readme&utm_id=features-text)) | ✅ Available & Production Ready by August |
|
||||
| 📱 Picture in Picture | ✅ Available & Production Ready by August |
|
||||
| 🎚️ Fine-grained control over tracks, buffering & events | 🏗️ In Development |
|
||||
| 🧠 Advanced control over playback and buffering | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4604) |
|
||||
| 🔐 DRM: Widevine & FairPlay ([See free DRM stream example](https://www.thewidlarzgroup.com/services/free-drm-token-generator-for-video?utm_source=rnv&utm_medium=readme&utm_id=free-drm)) | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4606) |
|
||||
| 🌐 Basic Web Support | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4605) |
|
||||
| 📺 TV Support | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4607) |
|
||||
| 🥽 VisionOS Support | 📝 [TODO](https://github.com/TheWidlarzGroup/react-native-video/issues/4608) |
|
||||
|
||||
```javascript
|
||||
// Load the module
|
||||
|
||||
import Video, {VideoRef} from 'react-native-video';
|
||||
|
||||
// Within your render function, assuming you have a file called
|
||||
// "background.mp4" in your project. You can include multiple videos
|
||||
// on a single screen if you like.
|
||||
## ✨ Project Status
|
||||
|
||||
const VideoPlayer = () => {
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
const background = require('./background.mp4');
|
||||
| Version | State | Architecture |
|
||||
|---------|-------|--------------|
|
||||
| **v5 and lower** | ❌ End-of-life [Commercial Support Available](https://www.thewidlarzgroup.com/blog/react-native-video-upgrade-challenges-custom-maintenance-support#how-we-can-help?utm_source=rnv&utm_medium=readme&utm_id=upgradev5) | Old Architecture |
|
||||
| **v6** | 🛠 Maintained (community + TWG) | Old + New (Interop Layer) |
|
||||
| **v7** | 🚀 Active Development | Old + New (Full Support) |
|
||||
|
||||
return (
|
||||
<Video
|
||||
// Can be a URL or a local file.
|
||||
source={background}
|
||||
// Store reference
|
||||
ref={videoRef}
|
||||
// Callback when remote video is buffering
|
||||
onBuffer={onBuffer}
|
||||
// Callback when video cannot be loaded
|
||||
onError={onError}
|
||||
style={styles.backgroundVideo}
|
||||
/>
|
||||
)
|
||||
}
|
||||
`react-native-video` v7 introduces full support for the new React Native architecture, unlocking better performance, improved consistency, and modern native modules.
|
||||
|
||||
// Later on in your styles..
|
||||
var styles = StyleSheet.create({
|
||||
backgroundVideo: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
});
|
||||
---
|
||||
|
||||
## 📚 Documentation & Examples
|
||||
|
||||
- 📖 [Documentation](https://docs.thewidlarzgroup.com/react-native-video)
|
||||
- 📦 [Example: Basic Usage](https://github.com/TheWidlarzGroup/react-native-video/tree/v7/example)
|
||||
- 📦 [Example: Free DRM Stream](https://www.thewidlarzgroup.com/services/free-drm-token-generator-for-video?utm_source=rnv&utm_medium=readme&utm_id=free-drm)
|
||||
- 📦 Example: Offline SDK integration - In Progress 🏗️, will be available soon
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Requirements
|
||||
|
||||
- React Native 0.75 or higher
|
||||
- `react-native-nitro-modules` (>=0.27.2) - Please see [nitro requirements](https://nitro.margelo.com/docs/minimum-requirements)
|
||||
|
||||
### Install
|
||||
|
||||
`react-native-video` requires `react-native-nitro-modules` (>=0.27.2) in your project.
|
||||
```bash
|
||||
npm install react-native-nitro-modules
|
||||
```
|
||||
|
||||
## Supported by
|
||||
<p>
|
||||
📱 TWG provides both free and commercial support for this project. Feel free to contact us 🤝 to build something awesome together! 🚀
|
||||
</p>
|
||||
<a href="https://thewidlarzgroup.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./docs/assets/baners/twg-dark.png" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="./docs/assets/baners/twg-light.png" />
|
||||
<img alt="TheWidlarzGroup" src="./docs/assets/baners/twg-light-1.png" />
|
||||
</picture>
|
||||
</a>
|
||||
Then install `react-native-video`
|
||||
|
||||
```bash
|
||||
# Install the alpha version of react-native-video v7
|
||||
npm install react-native-video@next
|
||||
|
||||
# Install pods
|
||||
cd ios && pod install
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>For react-native < 0.80</summary>
|
||||
`react-native` < 0.80 have bug that prevents to properly handle errors by nitro modules on Android.
|
||||
We highly recommend to apply bellow patch for `react-native-nitro-modules` to fix this issue.
|
||||
You can apply it using `patch-package`.
|
||||
|
||||
Without this patch you won't be able "recognize" errors, all will be thrown as unknown errors.
|
||||
|
||||
see [installation guide](https://docs.thewidlarzgroup.com/react-native-video/docs/v7/installation#patch-for-react-native--080)
|
||||
</details>
|
||||
|
||||
### Usage
|
||||
```tsx
|
||||
import { useVideoPlayer, VideoView } from 'react-native-video';
|
||||
|
||||
export default () => (
|
||||
const player = useVideoPlayer(
|
||||
'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
(_player) => {
|
||||
_player.play();
|
||||
}
|
||||
);
|
||||
|
||||
<VideoView
|
||||
player={player}
|
||||
style={{ width: '100%', aspectRatio: 16 / 9 }}
|
||||
controls
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Plugins
|
||||
|
||||
<a href="https://www.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=readme&utm_id=banner">
|
||||
<img src="./docs/static/baners/sdk-banner.png" alt="Offline SDK Preview" width="40%" align="right" />
|
||||
</a>
|
||||
|
||||
### 1 · 📥 Offline SDK
|
||||
|
||||
Enable offline streaming with full control over downloads, license lifecycle, secure storage, and media access.
|
||||
|
||||
- Track selection (bitrate, audio, subtitles)
|
||||
- Pause / resume & background queueing
|
||||
- Expiration & auto-cleanup
|
||||
- Built for Android & iOS
|
||||
- → [Read the SDK Docs](https://docs.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=readme&utm_id=modules-sdk-text)
|
||||
|
||||
### 2 · 🧪 Architecture
|
||||
|
||||
Write your own plugins to extend library logic, attach analytics or add custom workflows - **without forking** the core SDK.
|
||||
→ [Plugin documentation](https://docs.thewidlarzgroup.com/react-native-video/other/plugin)
|
||||
|
||||
---
|
||||
|
||||
## 💼 TWG Services & Products
|
||||
|
||||
| Offering | Description |
|
||||
|----------|-------------|
|
||||
| [**Professional Support Packages**](https://www.thewidlarzgroup.com/issue-boost?utm_source=rnv&utm_medium=readme&utm_campaign=professional-support-packages#Contact) | Priority bug-fixes, guaranteed SLAs, [roadmap influence](https://github.com/orgs/TheWidlarzGroup/projects/6) |
|
||||
| [**Issue Booster**](https://www.thewidlarzgroup.com/issue-boost?utm_source=rnv&utm_medium=readme) | Fast-track urgent fixes with a pay‑per‑issue model |
|
||||
| [**Offline Video SDK**](https://www.thewidlarzgroup.com/offline-video-sdk/?utm_source=rnv&utm_medium=readme&utm_campaign=downloading&utm_id=offline-video-sdk-link) | Plug‑and‑play secure download solution for iOS & Android |
|
||||
| [**Integration Support**](https://www.thewidlarzgroup.com/?utm_source=rnv&utm_medium=readme&utm_campaign=integration-support#Contact) | Hands‑on help integrating video, DRM & offline into your app |
|
||||
| [**Free DRM Token Generator**](https://www.thewidlarzgroup.com/services/free-drm-token-generator-for-video?utm_source=rnv&utm_medium=readme&utm_id=free-drm) | Generate Widevine / FairPlay tokens for testing |
|
||||
| [**Ready Boilerplates**](https://www.thewidlarzgroup.com/showcases?utm_source=rnv&utm_medium=readme) | Ready-to-use apps with offline HLS/DASH DRM, video frame scrubbing, TikTok-style video feed, background uploads, Skia-based frame processor (R&D phase), and more |
|
||||
| [**React Native Video Upgrade Guide**](https://www.thewidlarzgroup.com/blog/react-native-video-upgrade-challenges-custom-maintenance-support?utm_source=rnv&utm_medium=readme&utm_id=upgrade-blog&utm_campaign=v7) | Common upgrade pitfalls & how to solve them |
|
||||
|
||||
*See how [TWG](https://www.thewidlarzgroup.com/?utm_source=rnv&utm_medium=readme&utm_id=services-text) helped **Learnn** ship a world‑class player in record time - [case study](https://gitnation.com/contents/a-4-year-retrospective-lessons-learned-from-building-a-video-player-from-scratch-with-react-native).*
|
||||
|
||||
Contact us at [hi@thewidlarzgroup.com](mailto:hi@thewidlarzgroup.com)
|
||||
|
||||
## 🌍 Social
|
||||
|
||||
- 🐦 **X / Twitter** - [follow product & release updates](https://x.com/TheWidlarzGroup)
|
||||
- 💬 **Discord** - [talk to the community and us](https://discord.gg/9WPq6Yx)
|
||||
- 💼 **LinkedIn** - [see TWG flexing](https://linkedin.com/company/the-widlarz-group)
|
||||
|
||||
## 📰 Community & Media
|
||||
|
||||
- 🗽 **React Summit US** – How TWG helped Learnn boost video performance on React Native.
|
||||
[Watch the talk »](https://gitnation.com/contents/a-4-year-retrospective-lessons-learned-from-building-a-video-player-from-scratch-with-react-native)
|
||||
|
||||
- 🧨 **v7 deep dive** – Why we’re building v7 with Nitro Modules
|
||||
[Watch on X »](https://x.com/krzysztof_moch/status/1854162551946478051)
|
||||
|
||||
- 🛠️ **Well-maintained open-source library** - What does it truly mean? - Bart's talk for React Native Warsaw
|
||||
[Watch here »](https://www.youtube.com/watch?v=RAQQwGCQNqY)
|
||||
|
||||
- 📺 **“Over the Top” Panel** - Building Streaming Apps for Mobile, Web, and Smart TVs - Bart giving his insights on the industry
|
||||
[Watch here »](https://youtu.be/j2b_bG-32JI)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
[*.{kt,kts}]
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
continuation_indent_size=4
|
||||
insert_final_newline=true
|
||||
max_line_length=160
|
||||
ktlint_code_style=android_studio
|
||||
ktlint_standard=enabled
|
||||
ktlint_experimental=enabled
|
||||
ktlint_standard_filename=disabled # dont require PascalCase filenames
|
||||
ktlint_standard_no-wildcard-imports=disabled # allow .* imports
|
||||
ktlint_function_signature_body_expression_wrapping=multiline
|
||||
ij_kotlin_allow_trailing_comma_on_call_site=false
|
||||
ij_kotlin_allow_trailing_comma=false
|
||||
@@ -1,46 +0,0 @@
|
||||
## react-native-video - ExoPlayer
|
||||
|
||||
This is an Android React Native video component based on ExoPlayer v2.
|
||||
|
||||
> ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates.
|
||||
|
||||
https://github.com/google/ExoPlayer
|
||||
|
||||
## Benefits over `react-native-video@0.9.0`:
|
||||
|
||||
- Android Video library built by Google, with a lot of support
|
||||
- Supports DASH, HLS, & SmoothStreaming adaptive streams
|
||||
- Supports formats such as MP4, M4A, FMP4, WebM, MKV, MP3, Ogg, WAV, MPEG-TS, MPEG-PS, FLV and ADTS (AAC).
|
||||
- Fewer device specific issues
|
||||
- Highly customisable
|
||||
|
||||
## ExoPlayer only props
|
||||
|
||||
```javascript
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Video
|
||||
...
|
||||
disableFocus={true} // disables audio focus and wake lock (default false)
|
||||
onAudioBecomingNoisy={this.onAudioBecomingNoisy} // Callback when audio is becoming noisy - should pause video
|
||||
onAudioFocusChanged={this.onAudioFocusChanged} // Callback when audio focus has been lost - pause if focus has been lost
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
onAudioBecomingNoisy = () => {
|
||||
this.setState({ pause: true })
|
||||
}
|
||||
|
||||
onAudioFocusChanged = (event: { hasAudioFocus: boolean }) => {
|
||||
if (!this.state.paused && !event.hasAudioFocus) {
|
||||
this.setState({ paused: true })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Unimplemented props
|
||||
|
||||
- Expansion file - `source={{ mainVer: 1, patchVer: 0 }}`
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
import com.android.Version
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
buildscript {
|
||||
def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['RNVideo_kotlinVersion']
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
}
|
||||
|
||||
def safeExtGet(prop) {
|
||||
return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : project.properties["RNVideo_" + prop]
|
||||
}
|
||||
|
||||
def isNewArchitectureEnabled() {
|
||||
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
||||
}
|
||||
|
||||
def supportsNamespace() {
|
||||
def parsed = Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
||||
def major = parsed[0].toInteger()
|
||||
def minor = parsed[1].toInteger()
|
||||
|
||||
// Namespace support was added in 7.3.0
|
||||
if (major == 7 && minor >= 3) {
|
||||
return true
|
||||
}
|
||||
|
||||
return major >= 8
|
||||
}
|
||||
|
||||
def useExoplayerIMA = safeExtGet("RNVUseExoplayerIMA")?.toBoolean() ?: false
|
||||
|
||||
println "useExoplayerIMA:" + useExoplayerIMA
|
||||
|
||||
// This string is used to define build path.
|
||||
// As react native build output directory is react-native path of the module.
|
||||
// We need to force a new path on each configuration change.
|
||||
// If you add a new build parameter, please add the new value in this string
|
||||
def configStringPath = (
|
||||
'useExoplayerIMA' + useExoplayerIMA \
|
||||
).md5()
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
apply plugin: "com.facebook.react"
|
||||
}
|
||||
|
||||
android {
|
||||
if (supportsNamespace()) {
|
||||
namespace 'com.brentvatne.react'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile "src/main/AndroidManifestNew.xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileSdkVersion safeExtGet('compileSdkVersion')
|
||||
buildToolsVersion safeExtGet('buildToolsVersion')
|
||||
|
||||
def agpVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION
|
||||
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion safeExtGet('minSdkVersion')
|
||||
targetSdkVersion safeExtGet('targetSdkVersion')
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||
|
||||
ndk {
|
||||
abiFilters(*reactNativeArchitectures())
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude "**/libreact_render*.so"
|
||||
}
|
||||
|
||||
buildDir 'buildOutput_' + configStringPath
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
if (useExoplayerIMA) {
|
||||
exclude 'com/google/ads/interactivemedia/v3/api'
|
||||
exclude 'androidx/media3/exoplayer/ima'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java {
|
||||
if (isNewArchitectureEnabled()) {
|
||||
srcDirs += [
|
||||
"src/fabric/java",
|
||||
"${project.buildDir}/generated/source/codegen/java"
|
||||
]
|
||||
} else {
|
||||
srcDirs += [
|
||||
"src/oldarch/java"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def reactNativeArchitectures() {
|
||||
def value = project.getProperties().get("reactNativeArchitectures")
|
||||
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url "$rootDir/../node_modules/react-native/android"
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
def media3_version = safeExtGet('media3Version')
|
||||
def kotlin_version = safeExtGet('kotlinVersion')
|
||||
def androidxCore_version = safeExtGet('androidxCoreVersion')
|
||||
def androidxActivity_version = safeExtGet('androidxActivityVersion')
|
||||
|
||||
dependencies {
|
||||
// For < 0.71, this will be from the local maven repo
|
||||
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation "com.facebook.react:react-native:+"
|
||||
|
||||
implementation "androidx.core:core:$androidxCore_version"
|
||||
implementation "androidx.activity:activity-ktx:$androidxActivity_version"
|
||||
|
||||
// For media playback using ExoPlayer
|
||||
implementation "androidx.media3:media3-exoplayer:$media3_version"
|
||||
|
||||
// For Smooth Streaming playback support with ExoPlayer
|
||||
implementation "androidx.media3:media3-exoplayer-smoothstreaming:$media3_version"
|
||||
// For DASH playback support with ExoPlayer
|
||||
implementation "androidx.media3:media3-exoplayer-dash:$media3_version"
|
||||
// For HLS playback support with ExoPlayer
|
||||
implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
|
||||
// For ad insertion using the Interactive Media Ads SDK with ExoPlayer
|
||||
if (useExoplayerIMA) {
|
||||
implementation "androidx.media3:media3-exoplayer-ima:$media3_version"
|
||||
}
|
||||
|
||||
// For loading data using the OkHttp network stack
|
||||
implementation "androidx.media3:media3-datasource-okhttp:$media3_version"
|
||||
|
||||
// For building media playback UIs
|
||||
implementation "androidx.media3:media3-ui:$media3_version"
|
||||
|
||||
// For exposing and controlling media sessions
|
||||
implementation "androidx.media3:media3-session:$media3_version"
|
||||
|
||||
// Common functionality for loading data
|
||||
implementation "androidx.media3:media3-datasource:$media3_version"
|
||||
// Common functionality used across multiple media libraries
|
||||
implementation "androidx.media3:media3-common:$media3_version"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
RNVideo_kotlinVersion=1.7.0
|
||||
RNVideo_minSdkVersion=21
|
||||
RNVideo_targetSdkVersion=31
|
||||
RNVideo_compileSdkVersion=31
|
||||
RNVideo_ndkversion=21.4.7075529
|
||||
RNVideo_buildToolsVersion=30.0.2
|
||||
RNVideo_media3Version=1.1.1
|
||||
RNVideo_RNVUseExoplayerIMA=false
|
||||
RNVideo_androidxCoreVersion=1.9.0
|
||||
RNVideo_androidxActivityVersion=1.7.0
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<lint>
|
||||
<issue id="UnsafeOptInUsageError">
|
||||
<ignore regexp='\(markerClass = androidx\.media3\.common\.util\.UnstableApi\.class\)' />
|
||||
</issue>
|
||||
</lint>
|
||||
@@ -1,63 +0,0 @@
|
||||
package androidx.media3.exoplayer.ima;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.AdViewProvider;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
||||
import androidx.media3.exoplayer.source.ads.AdsMediaSource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImaAdsLoader implements AdsLoader {
|
||||
public void setPlayer(ExoPlayer ignoredPlayer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayer(@Nullable Player player) {
|
||||
}
|
||||
|
||||
public void release() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSupportedContentTypes(@NonNull int... ints) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(@NonNull AdsMediaSource adsMediaSource, @NonNull DataSpec dataSpec, @NonNull Object adsId, @NonNull AdViewProvider adViewProvider, @NonNull EventListener eventListener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(@NonNull AdsMediaSource adsMediaSource, @NonNull EventListener eventListener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePrepareComplete(@NonNull AdsMediaSource adsMediaSource, int i, int i1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePrepareError(@NonNull AdsMediaSource adsMediaSource, int i, int i1, @NonNull IOException e) {
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
public Builder(Context ignoredThemedReactContext) {
|
||||
}
|
||||
|
||||
public Builder setAdEventListener(Object ignoredReactExoplayerView) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAdErrorListener(Object ignoredReactExoplayerView) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImaAdsLoader build() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.brentvatne.common.api
|
||||
|
||||
import androidx.annotation.IntDef
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
|
||||
internal object ResizeMode {
|
||||
/**
|
||||
* Either the width or height is decreased to obtain the desired aspect ratio.
|
||||
*/
|
||||
const val RESIZE_MODE_FIT = 0
|
||||
|
||||
/**
|
||||
* The width is fixed and the height is increased or decreased to obtain the desired aspect ratio.
|
||||
*/
|
||||
const val RESIZE_MODE_FIXED_WIDTH = 1
|
||||
|
||||
/**
|
||||
* The height is fixed and the width is increased or decreased to obtain the desired aspect ratio.
|
||||
*/
|
||||
const val RESIZE_MODE_FIXED_HEIGHT = 2
|
||||
|
||||
/**
|
||||
* The height and the width is increased or decreased to fit the size of the view.
|
||||
*/
|
||||
const val RESIZE_MODE_FILL = 3
|
||||
|
||||
/**
|
||||
* Keeps the aspect ratio but takes up the view's size.
|
||||
*/
|
||||
const val RESIZE_MODE_CENTER_CROP = 4
|
||||
|
||||
@JvmStatic
|
||||
@Mode
|
||||
fun toResizeMode(ordinal: Int): Int =
|
||||
when (ordinal) {
|
||||
RESIZE_MODE_FIXED_WIDTH -> RESIZE_MODE_FIXED_WIDTH
|
||||
RESIZE_MODE_FIXED_HEIGHT -> RESIZE_MODE_FIXED_HEIGHT
|
||||
RESIZE_MODE_FILL -> RESIZE_MODE_FILL
|
||||
RESIZE_MODE_CENTER_CROP -> RESIZE_MODE_CENTER_CROP
|
||||
RESIZE_MODE_FIT -> RESIZE_MODE_FIT
|
||||
else -> RESIZE_MODE_FIT
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(
|
||||
RESIZE_MODE_FIT,
|
||||
RESIZE_MODE_FIXED_WIDTH,
|
||||
RESIZE_MODE_FIXED_HEIGHT,
|
||||
RESIZE_MODE_FILL,
|
||||
RESIZE_MODE_CENTER_CROP
|
||||
)
|
||||
annotation class Mode
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.brentvatne.common.api
|
||||
|
||||
import com.brentvatne.common.toolbox.ReactBridgeUtils
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
|
||||
/**
|
||||
* Helper file to parse SubtitleStyle prop and build a dedicated class
|
||||
*/
|
||||
class SubtitleStyle private constructor() {
|
||||
var fontSize = -1
|
||||
private set
|
||||
var paddingLeft = 0
|
||||
private set
|
||||
var paddingRight = 0
|
||||
private set
|
||||
var paddingTop = 0
|
||||
private set
|
||||
var paddingBottom = 0
|
||||
private set
|
||||
var opacity = 1f
|
||||
private set
|
||||
|
||||
companion object {
|
||||
private const val PROP_FONT_SIZE_TRACK = "fontSize"
|
||||
private const val PROP_PADDING_BOTTOM = "paddingBottom"
|
||||
private const val PROP_PADDING_TOP = "paddingTop"
|
||||
private const val PROP_PADDING_LEFT = "paddingLeft"
|
||||
private const val PROP_PADDING_RIGHT = "paddingRight"
|
||||
private const val PROP_OPACITY = "opacity"
|
||||
|
||||
@JvmStatic
|
||||
fun parse(src: ReadableMap?): SubtitleStyle {
|
||||
val subtitleStyle = SubtitleStyle()
|
||||
subtitleStyle.fontSize = ReactBridgeUtils.safeGetInt(src, PROP_FONT_SIZE_TRACK, -1)
|
||||
subtitleStyle.paddingBottom = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_BOTTOM, 0)
|
||||
subtitleStyle.paddingTop = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_TOP, 0)
|
||||
subtitleStyle.paddingLeft = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_LEFT, 0)
|
||||
subtitleStyle.paddingRight = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_RIGHT, 0)
|
||||
subtitleStyle.opacity = ReactBridgeUtils.safeGetFloat(src, PROP_OPACITY, 1f)
|
||||
return subtitleStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.brentvatne.common.api
|
||||
|
||||
/*
|
||||
* class to handle timedEvent retrieved from the stream
|
||||
*/
|
||||
|
||||
class TimedMetadata(_identifier: String? = null, _value: String? = null) {
|
||||
var identifier: String? = _identifier
|
||||
var value: String? = _value
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.brentvatne.common.api
|
||||
|
||||
/*
|
||||
* internal representation of audio & text tracks
|
||||
*/
|
||||
class Track {
|
||||
var title: String? = null
|
||||
var mimeType: String? = null
|
||||
var language: String? = null
|
||||
var isSelected = false
|
||||
|
||||
// in bps available only on audio tracks
|
||||
var bitrate = 0
|
||||
var index = 0
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.brentvatne.common.api
|
||||
|
||||
/*
|
||||
* internal representation of audio & text tracks
|
||||
*/
|
||||
|
||||
class VideoTrack {
|
||||
var width = 0
|
||||
var height = 0
|
||||
var bitrate = 0
|
||||
var codecs = ""
|
||||
var id = -1
|
||||
var trackId = ""
|
||||
var isSelected = false
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
package com.brentvatne.common.react;
|
||||
|
||||
import androidx.annotation.StringDef;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.brentvatne.common.api.TimedMetadata;
|
||||
import com.brentvatne.common.api.Track;
|
||||
import com.brentvatne.common.api.VideoTrack;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.UIManager;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.UIManagerHelper;
|
||||
import com.facebook.react.uimanager.common.ViewUtil;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
public class VideoEventEmitter {
|
||||
|
||||
private final ReactContext mReactContext;
|
||||
|
||||
private int viewId = View.NO_ID;
|
||||
|
||||
public VideoEventEmitter(ReactContext reactContext) {
|
||||
this.mReactContext = reactContext;
|
||||
}
|
||||
|
||||
private static final String EVENT_LOAD_START = "onVideoLoadStart";
|
||||
private static final String EVENT_LOAD = "onVideoLoad";
|
||||
private static final String EVENT_ERROR = "onVideoError";
|
||||
private static final String EVENT_PROGRESS = "onVideoProgress";
|
||||
private static final String EVENT_BANDWIDTH = "onVideoBandwidthUpdate";
|
||||
private static final String EVENT_SEEK = "onVideoSeek";
|
||||
private static final String EVENT_END = "onVideoEnd";
|
||||
private static final String EVENT_FULLSCREEN_WILL_PRESENT = "onVideoFullscreenPlayerWillPresent";
|
||||
private static final String EVENT_FULLSCREEN_DID_PRESENT = "onVideoFullscreenPlayerDidPresent";
|
||||
private static final String EVENT_FULLSCREEN_WILL_DISMISS = "onVideoFullscreenPlayerWillDismiss";
|
||||
private static final String EVENT_FULLSCREEN_DID_DISMISS = "onVideoFullscreenPlayerDidDismiss";
|
||||
|
||||
private static final String EVENT_STALLED = "onPlaybackStalled";
|
||||
private static final String EVENT_RESUME = "onPlaybackResume";
|
||||
private static final String EVENT_READY = "onReadyForDisplay";
|
||||
private static final String EVENT_BUFFER = "onVideoBuffer";
|
||||
private static final String EVENT_PLAYBACK_STATE_CHANGED = "onVideoPlaybackStateChanged";
|
||||
private static final String EVENT_IDLE = "onVideoIdle";
|
||||
private static final String EVENT_TIMED_METADATA = "onTimedMetadata";
|
||||
private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy";
|
||||
private static final String EVENT_AUDIO_FOCUS_CHANGE = "onAudioFocusChanged";
|
||||
private static final String EVENT_PLAYBACK_RATE_CHANGE = "onPlaybackRateChange";
|
||||
private static final String EVENT_VOLUME_CHANGE = "onVolumeChange";
|
||||
private static final String EVENT_AUDIO_TRACKS = "onAudioTracks";
|
||||
private static final String EVENT_TEXT_TRACKS = "onTextTracks";
|
||||
|
||||
private static final String EVENT_TEXT_TRACK_DATA_CHANGED = "onTextTrackDataChanged";
|
||||
private static final String EVENT_VIDEO_TRACKS = "onVideoTracks";
|
||||
private static final String EVENT_ON_RECEIVE_AD_EVENT = "onReceiveAdEvent";
|
||||
|
||||
static public final String[] Events = {
|
||||
EVENT_LOAD_START,
|
||||
EVENT_LOAD,
|
||||
EVENT_ERROR,
|
||||
EVENT_PROGRESS,
|
||||
EVENT_SEEK,
|
||||
EVENT_END,
|
||||
EVENT_FULLSCREEN_WILL_PRESENT,
|
||||
EVENT_FULLSCREEN_DID_PRESENT,
|
||||
EVENT_FULLSCREEN_WILL_DISMISS,
|
||||
EVENT_FULLSCREEN_DID_DISMISS,
|
||||
EVENT_STALLED,
|
||||
EVENT_RESUME,
|
||||
EVENT_READY,
|
||||
EVENT_BUFFER,
|
||||
EVENT_PLAYBACK_STATE_CHANGED,
|
||||
EVENT_IDLE,
|
||||
EVENT_TIMED_METADATA,
|
||||
EVENT_AUDIO_BECOMING_NOISY,
|
||||
EVENT_AUDIO_FOCUS_CHANGE,
|
||||
EVENT_PLAYBACK_RATE_CHANGE,
|
||||
EVENT_VOLUME_CHANGE,
|
||||
EVENT_AUDIO_TRACKS,
|
||||
EVENT_TEXT_TRACKS,
|
||||
EVENT_TEXT_TRACK_DATA_CHANGED,
|
||||
EVENT_VIDEO_TRACKS,
|
||||
EVENT_BANDWIDTH,
|
||||
EVENT_ON_RECEIVE_AD_EVENT
|
||||
};
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@StringDef({
|
||||
EVENT_LOAD_START,
|
||||
EVENT_LOAD,
|
||||
EVENT_ERROR,
|
||||
EVENT_PROGRESS,
|
||||
EVENT_SEEK,
|
||||
EVENT_END,
|
||||
EVENT_FULLSCREEN_WILL_PRESENT,
|
||||
EVENT_FULLSCREEN_DID_PRESENT,
|
||||
EVENT_FULLSCREEN_WILL_DISMISS,
|
||||
EVENT_FULLSCREEN_DID_DISMISS,
|
||||
EVENT_STALLED,
|
||||
EVENT_RESUME,
|
||||
EVENT_READY,
|
||||
EVENT_BUFFER,
|
||||
EVENT_PLAYBACK_STATE_CHANGED,
|
||||
EVENT_IDLE,
|
||||
EVENT_TIMED_METADATA,
|
||||
EVENT_AUDIO_BECOMING_NOISY,
|
||||
EVENT_AUDIO_FOCUS_CHANGE,
|
||||
EVENT_PLAYBACK_RATE_CHANGE,
|
||||
EVENT_VOLUME_CHANGE,
|
||||
EVENT_AUDIO_TRACKS,
|
||||
EVENT_TEXT_TRACKS,
|
||||
EVENT_TEXT_TRACK_DATA_CHANGED,
|
||||
EVENT_VIDEO_TRACKS,
|
||||
EVENT_BANDWIDTH,
|
||||
EVENT_ON_RECEIVE_AD_EVENT
|
||||
})
|
||||
@interface VideoEvents {
|
||||
}
|
||||
|
||||
private static final String EVENT_PROP_FAST_FORWARD = "canPlayFastForward";
|
||||
private static final String EVENT_PROP_SLOW_FORWARD = "canPlaySlowForward";
|
||||
private static final String EVENT_PROP_SLOW_REVERSE = "canPlaySlowReverse";
|
||||
private static final String EVENT_PROP_REVERSE = "canPlayReverse";
|
||||
private static final String EVENT_PROP_STEP_FORWARD = "canStepForward";
|
||||
private static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward";
|
||||
|
||||
private static final String EVENT_PROP_DURATION = "duration";
|
||||
private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
|
||||
private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration";
|
||||
private static final String EVENT_PROP_CURRENT_TIME = "currentTime";
|
||||
private static final String EVENT_PROP_CURRENT_PLAYBACK_TIME = "currentPlaybackTime";
|
||||
private static final String EVENT_PROP_SEEK_TIME = "seekTime";
|
||||
private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize";
|
||||
private static final String EVENT_PROP_TRACK_ID = "trackId";
|
||||
private static final String EVENT_PROP_WIDTH = "width";
|
||||
private static final String EVENT_PROP_HEIGHT = "height";
|
||||
private static final String EVENT_PROP_ORIENTATION = "orientation";
|
||||
private static final String EVENT_PROP_VIDEO_TRACKS = "videoTracks";
|
||||
private static final String EVENT_PROP_AUDIO_TRACKS = "audioTracks";
|
||||
private static final String EVENT_PROP_TEXT_TRACKS = "textTracks";
|
||||
private static final String EVENT_PROP_TEXT_TRACK_DATA = "subtitleTracks";
|
||||
private static final String EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus";
|
||||
private static final String EVENT_PROP_IS_BUFFERING = "isBuffering";
|
||||
private static final String EVENT_PROP_PLAYBACK_RATE = "playbackRate";
|
||||
private static final String EVENT_PROP_VOLUME = "volume";
|
||||
|
||||
private static final String EVENT_PROP_ERROR = "error";
|
||||
private static final String EVENT_PROP_ERROR_STRING = "errorString";
|
||||
private static final String EVENT_PROP_ERROR_EXCEPTION = "errorException";
|
||||
private static final String EVENT_PROP_ERROR_TRACE = "errorStackTrace";
|
||||
private static final String EVENT_PROP_ERROR_CODE = "errorCode";
|
||||
|
||||
private static final String EVENT_PROP_TIMED_METADATA = "metadata";
|
||||
|
||||
private static final String EVENT_PROP_BITRATE = "bitrate";
|
||||
|
||||
private static final String EVENT_PROP_IS_PLAYING = "isPlaying";
|
||||
|
||||
public void setViewId(int viewId) {
|
||||
this.viewId = viewId;
|
||||
}
|
||||
|
||||
public void loadStart() {
|
||||
receiveEvent(EVENT_LOAD_START, null);
|
||||
}
|
||||
|
||||
WritableMap aspectRatioToNaturalSize(int videoWidth, int videoHeight) {
|
||||
WritableMap naturalSize = Arguments.createMap();
|
||||
naturalSize.putInt(EVENT_PROP_WIDTH, videoWidth);
|
||||
naturalSize.putInt(EVENT_PROP_HEIGHT, videoHeight);
|
||||
if (videoWidth > videoHeight) {
|
||||
naturalSize.putString(EVENT_PROP_ORIENTATION, "landscape");
|
||||
} else if (videoWidth < videoHeight) {
|
||||
naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait");
|
||||
} else {
|
||||
naturalSize.putString(EVENT_PROP_ORIENTATION, "square");
|
||||
}
|
||||
return naturalSize;
|
||||
}
|
||||
|
||||
WritableArray audioTracksToArray(ArrayList<Track> audioTracks) {
|
||||
WritableArray waAudioTracks = Arguments.createArray();
|
||||
if( audioTracks != null ){
|
||||
for (int i = 0; i < audioTracks.size(); ++i) {
|
||||
Track format = audioTracks.get(i);
|
||||
WritableMap audioTrack = Arguments.createMap();
|
||||
audioTrack.putInt("index", i);
|
||||
audioTrack.putString("title", format.getTitle());
|
||||
audioTrack.putString("type", format.getMimeType());
|
||||
audioTrack.putString("language", format.getLanguage());
|
||||
audioTrack.putInt("bitrate", format.getBitrate());
|
||||
audioTrack.putBoolean("selected", format.isSelected());
|
||||
waAudioTracks.pushMap(audioTrack);
|
||||
}
|
||||
}
|
||||
return waAudioTracks;
|
||||
}
|
||||
|
||||
WritableArray videoTracksToArray(ArrayList<VideoTrack> videoTracks) {
|
||||
WritableArray waVideoTracks = Arguments.createArray();
|
||||
if( videoTracks != null ){
|
||||
for (int i = 0; i < videoTracks.size(); ++i) {
|
||||
VideoTrack vTrack = videoTracks.get(i);
|
||||
WritableMap videoTrack = Arguments.createMap();
|
||||
videoTrack.putInt("width", vTrack.getWidth());
|
||||
videoTrack.putInt("height",vTrack.getHeight());
|
||||
videoTrack.putInt("bitrate", vTrack.getBitrate());
|
||||
videoTrack.putString("codecs", vTrack.getCodecs());
|
||||
videoTrack.putInt("trackId",vTrack.getId());
|
||||
videoTrack.putBoolean("selected", vTrack.isSelected());
|
||||
waVideoTracks.pushMap(videoTrack);
|
||||
}
|
||||
}
|
||||
return waVideoTracks;
|
||||
}
|
||||
|
||||
WritableArray textTracksToArray(ArrayList<Track> textTracks) {
|
||||
WritableArray waTextTracks = Arguments.createArray();
|
||||
if (textTracks != null) {
|
||||
for (int i = 0; i < textTracks.size(); ++i) {
|
||||
Track format = textTracks.get(i);
|
||||
WritableMap textTrack = Arguments.createMap();
|
||||
textTrack.putInt("index", i);
|
||||
textTrack.putString("title", format.getTitle());
|
||||
textTrack.putString("type", format.getMimeType());
|
||||
textTrack.putString("language", format.getLanguage());
|
||||
textTrack.putBoolean("selected", format.isSelected());
|
||||
waTextTracks.pushMap(textTrack);
|
||||
}
|
||||
}
|
||||
return waTextTracks;
|
||||
}
|
||||
|
||||
public void load(double duration, double currentPosition, int videoWidth, int videoHeight,
|
||||
ArrayList<Track> audioTracks, ArrayList<Track> textTracks, ArrayList<VideoTrack> videoTracks, String trackId){
|
||||
WritableArray waAudioTracks = audioTracksToArray(audioTracks);
|
||||
WritableArray waVideoTracks = videoTracksToArray(videoTracks);
|
||||
WritableArray waTextTracks = textTracksToArray(textTracks);
|
||||
|
||||
load( duration, currentPosition, videoWidth, videoHeight, waAudioTracks, waTextTracks, waVideoTracks, trackId);
|
||||
}
|
||||
|
||||
void load(double duration, double currentPosition, int videoWidth, int videoHeight,
|
||||
WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks, String trackId) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
|
||||
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
|
||||
|
||||
WritableMap naturalSize = aspectRatioToNaturalSize(videoWidth, videoHeight);
|
||||
event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize);
|
||||
event.putString(EVENT_PROP_TRACK_ID, trackId);
|
||||
event.putArray(EVENT_PROP_VIDEO_TRACKS, videoTracks);
|
||||
event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks);
|
||||
event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks);
|
||||
|
||||
// TODO: Actually check if you can.
|
||||
event.putBoolean(EVENT_PROP_FAST_FORWARD, true);
|
||||
event.putBoolean(EVENT_PROP_SLOW_FORWARD, true);
|
||||
event.putBoolean(EVENT_PROP_SLOW_REVERSE, true);
|
||||
event.putBoolean(EVENT_PROP_REVERSE, true);
|
||||
event.putBoolean(EVENT_PROP_FAST_FORWARD, true);
|
||||
event.putBoolean(EVENT_PROP_STEP_BACKWARD, true);
|
||||
event.putBoolean(EVENT_PROP_STEP_FORWARD, true);
|
||||
|
||||
receiveEvent(EVENT_LOAD, event);
|
||||
}
|
||||
|
||||
WritableMap arrayToObject(String field, WritableArray array) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putArray(field, array);
|
||||
return event;
|
||||
}
|
||||
|
||||
public void audioTracks(ArrayList<Track> audioTracks){
|
||||
receiveEvent(EVENT_AUDIO_TRACKS, arrayToObject(EVENT_PROP_AUDIO_TRACKS, audioTracksToArray(audioTracks)));
|
||||
}
|
||||
|
||||
public void textTracks(ArrayList<Track> textTracks){
|
||||
receiveEvent(EVENT_TEXT_TRACKS, arrayToObject(EVENT_PROP_TEXT_TRACKS, textTracksToArray(textTracks)));
|
||||
}
|
||||
|
||||
public void textTrackDataChanged(String textTrackData){
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putString(EVENT_PROP_TEXT_TRACK_DATA, textTrackData);
|
||||
receiveEvent(EVENT_TEXT_TRACK_DATA_CHANGED, event);
|
||||
}
|
||||
|
||||
public void videoTracks(ArrayList<VideoTrack> videoTracks){
|
||||
receiveEvent(EVENT_VIDEO_TRACKS, arrayToObject(EVENT_PROP_VIDEO_TRACKS, videoTracksToArray(videoTracks)));
|
||||
}
|
||||
|
||||
public void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration, double currentPlaybackTime) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
|
||||
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000D);
|
||||
event.putDouble(EVENT_PROP_SEEKABLE_DURATION, seekableDuration / 1000D);
|
||||
event.putDouble(EVENT_PROP_CURRENT_PLAYBACK_TIME, currentPlaybackTime);
|
||||
receiveEvent(EVENT_PROGRESS, event);
|
||||
}
|
||||
|
||||
public void bandwidthReport(double bitRateEstimate, int height, int width, String id) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble(EVENT_PROP_BITRATE, bitRateEstimate);
|
||||
event.putInt(EVENT_PROP_WIDTH, width);
|
||||
event.putInt(EVENT_PROP_HEIGHT, height);
|
||||
event.putString(EVENT_PROP_TRACK_ID, id);
|
||||
receiveEvent(EVENT_BANDWIDTH, event);
|
||||
}
|
||||
|
||||
public void seek(long currentPosition, long seekTime) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
|
||||
event.putDouble(EVENT_PROP_SEEK_TIME, seekTime / 1000D);
|
||||
receiveEvent(EVENT_SEEK, event);
|
||||
}
|
||||
|
||||
public void ready() {
|
||||
receiveEvent(EVENT_READY, null);
|
||||
}
|
||||
|
||||
public void buffering(boolean isBuffering) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putBoolean(EVENT_PROP_IS_BUFFERING, isBuffering);
|
||||
receiveEvent(EVENT_BUFFER, map);
|
||||
}
|
||||
|
||||
public void playbackStateChanged(boolean isPlaying) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putBoolean(EVENT_PROP_IS_PLAYING, isPlaying);
|
||||
receiveEvent(EVENT_PLAYBACK_STATE_CHANGED, map);
|
||||
}
|
||||
|
||||
public void idle() {
|
||||
receiveEvent(EVENT_IDLE, null);
|
||||
}
|
||||
|
||||
public void end() {
|
||||
receiveEvent(EVENT_END, null);
|
||||
}
|
||||
|
||||
public void fullscreenWillPresent() {
|
||||
receiveEvent(EVENT_FULLSCREEN_WILL_PRESENT, null);
|
||||
}
|
||||
|
||||
public void fullscreenDidPresent() {
|
||||
receiveEvent(EVENT_FULLSCREEN_DID_PRESENT, null);
|
||||
}
|
||||
|
||||
public void fullscreenWillDismiss() {
|
||||
receiveEvent(EVENT_FULLSCREEN_WILL_DISMISS, null);
|
||||
}
|
||||
|
||||
public void fullscreenDidDismiss() {
|
||||
receiveEvent(EVENT_FULLSCREEN_DID_DISMISS, null);
|
||||
}
|
||||
|
||||
public void error(String errorString, Exception exception) {
|
||||
_error(errorString, exception, "0001");
|
||||
}
|
||||
|
||||
public void error(String errorString, Exception exception, String errorCode) {
|
||||
_error(errorString, exception, errorCode);
|
||||
}
|
||||
|
||||
void _error(String errorString, Exception exception, String errorCode) {
|
||||
// Prepare stack trace
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
exception.printStackTrace(pw);
|
||||
String stackTrace = sw.toString();
|
||||
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString(EVENT_PROP_ERROR_STRING, errorString);
|
||||
error.putString(EVENT_PROP_ERROR_EXCEPTION, exception.toString());
|
||||
error.putString(EVENT_PROP_ERROR_CODE, errorCode);
|
||||
error.putString(EVENT_PROP_ERROR_TRACE, stackTrace);
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putMap(EVENT_PROP_ERROR, error);
|
||||
receiveEvent(EVENT_ERROR, event);
|
||||
}
|
||||
|
||||
public void playbackRateChange(float rate) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putDouble(EVENT_PROP_PLAYBACK_RATE, (double)rate);
|
||||
receiveEvent(EVENT_PLAYBACK_RATE_CHANGE, map);
|
||||
}
|
||||
|
||||
public void volumeChange(float volume) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putDouble(EVENT_PROP_VOLUME, volume);
|
||||
receiveEvent(EVENT_VOLUME_CHANGE, map);
|
||||
}
|
||||
|
||||
public void timedMetadata(ArrayList<TimedMetadata> _metadataArrayList) {
|
||||
if (_metadataArrayList.size() == 0) {
|
||||
return;
|
||||
}
|
||||
WritableArray metadataArray = Arguments.createArray();
|
||||
|
||||
for (int i = 0; i < _metadataArrayList.size(); i++) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("identifier", _metadataArrayList.get(i).getIdentifier());
|
||||
map.putString("value", _metadataArrayList.get(i).getValue());
|
||||
metadataArray.pushMap(map);
|
||||
}
|
||||
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putArray(EVENT_PROP_TIMED_METADATA, metadataArray);
|
||||
receiveEvent(EVENT_TIMED_METADATA, event);
|
||||
}
|
||||
|
||||
public void audioFocusChanged(boolean hasFocus) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putBoolean(EVENT_PROP_HAS_AUDIO_FOCUS, hasFocus);
|
||||
receiveEvent(EVENT_AUDIO_FOCUS_CHANGE, map);
|
||||
}
|
||||
|
||||
public void audioBecomingNoisy() {
|
||||
receiveEvent(EVENT_AUDIO_BECOMING_NOISY, null);
|
||||
}
|
||||
|
||||
public void receiveAdEvent(String event, Map<String, String> data) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("event", event);
|
||||
|
||||
WritableMap dataMap = Arguments.createMap();
|
||||
for (Map.Entry<String, String> entry : data.entrySet()) {
|
||||
dataMap.putString(entry.getKey(), entry.getValue());
|
||||
}
|
||||
map.putMap("data", dataMap);
|
||||
|
||||
receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map);
|
||||
}
|
||||
|
||||
public void receiveAdEvent(String event) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("event", event);
|
||||
|
||||
receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map);
|
||||
}
|
||||
|
||||
public void receiveAdErrorEvent(String message, String code, String type) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putString("event", "ERROR");
|
||||
|
||||
WritableMap dataMap = Arguments.createMap();
|
||||
dataMap.putString("message", message);
|
||||
dataMap.putString("code", code);
|
||||
dataMap.putString("type", type);
|
||||
map.putMap("data", dataMap);
|
||||
|
||||
receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map);
|
||||
}
|
||||
|
||||
private void receiveEvent(@VideoEvents String type, WritableMap event) {
|
||||
UIManager uiManager = UIManagerHelper.getUIManager(mReactContext, ViewUtil.getUIManagerType(viewId));
|
||||
|
||||
if(uiManager != null) {
|
||||
uiManager.receiveEvent(UIManagerHelper.getSurfaceId(mReactContext), viewId, type, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package com.brentvatne.common.toolbox
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import java.lang.Exception
|
||||
|
||||
/* log utils
|
||||
* This class allow defining a log level for the package
|
||||
* This is useful for debugging real time issue or tricky use cases
|
||||
*/
|
||||
|
||||
object DebugLog {
|
||||
// log level to display
|
||||
private var level = Log.WARN
|
||||
|
||||
// enable thread display in logs
|
||||
private var displayThread = true
|
||||
|
||||
// add a common prefix for easy filtering
|
||||
private const val TAG_PREFIX = "RNV"
|
||||
|
||||
@JvmStatic
|
||||
fun setConfig(_level: Int, _displayThread: Boolean) {
|
||||
level = _level
|
||||
displayThread = _displayThread
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun getTag(tag: String): String = TAG_PREFIX + tag
|
||||
|
||||
@JvmStatic
|
||||
private fun getMsg(msg: String): String =
|
||||
if (displayThread) {
|
||||
"[" + Thread.currentThread().name + "] " + msg
|
||||
} else {
|
||||
msg
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun v(tag: String, msg: String) {
|
||||
if (level <= Log.VERBOSE) Log.v(getTag(tag), getMsg(msg))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun d(tag: String, msg: String) {
|
||||
if (level <= Log.DEBUG) Log.d(getTag(tag), getMsg(msg))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun i(tag: String, msg: String) {
|
||||
if (level <= Log.INFO) Log.i(getTag(tag), getMsg(msg))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun w(tag: String, msg: String) {
|
||||
if (level <= Log.WARN) Log.w(getTag(tag), getMsg(msg))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun e(tag: String, msg: String) {
|
||||
if (level <= Log.ERROR) Log.e(getTag(tag), getMsg(msg))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun wtf(tag: String, msg: String) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
||||
Log.wtf(getTag(tag), "--------------->" + getMsg(msg))
|
||||
} else {
|
||||
Log.e(getTag(tag), "--------------->" + getMsg(msg))
|
||||
}
|
||||
printCallStack()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun printCallStack() {
|
||||
if (level <= Log.VERBOSE) {
|
||||
val e = Exception()
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// Additionnal thread safety checkers
|
||||
@JvmStatic
|
||||
fun checkUIThread(tag: String, msg: String) {
|
||||
if (Thread.currentThread().name != "main") {
|
||||
wtf(tag, "------------------------>" + getMsg(msg))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun checkNotUIThread(tag: String, msg: String) {
|
||||
if (Thread.currentThread().name == "main") {
|
||||
wtf(tag, "------------------------>" + getMsg(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package com.brentvatne.common.toolbox
|
||||
|
||||
import com.facebook.react.bridge.Dynamic
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import java.util.HashMap
|
||||
|
||||
/*
|
||||
* Toolbox to safe parsing of <Video props
|
||||
* These are just safe accessors to ReadableMap
|
||||
*/
|
||||
|
||||
object ReactBridgeUtils {
|
||||
@JvmStatic
|
||||
fun safeGetString(map: ReadableMap?, key: String?, fallback: String?): String? =
|
||||
if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getString(key) else fallback
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetString(map: ReadableMap?, key: String?): String? = safeGetString(map, key, null)
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetDynamic(map: ReadableMap?, key: String?, fallback: Dynamic?): Dynamic? =
|
||||
if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getDynamic(key) else fallback
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetDynamic(map: ReadableMap?, key: String?): Dynamic? = safeGetDynamic(map, key, null)
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetBool(map: ReadableMap?, key: String?, fallback: Boolean): Boolean =
|
||||
if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getBoolean(key) else fallback
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetMap(map: ReadableMap?, key: String?): ReadableMap? = if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getMap(key) else null
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetArray(map: ReadableMap?, key: String?): ReadableArray? = if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getArray(key) else null
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetInt(map: ReadableMap?, key: String?, fallback: Int): Int =
|
||||
if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getInt(key) else fallback
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetInt(map: ReadableMap?, key: String?): Int = safeGetInt(map, key, 0)
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetDouble(map: ReadableMap?, key: String?, fallback: Double): Double =
|
||||
if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getDouble(key) else fallback
|
||||
|
||||
@JvmStatic
|
||||
fun safeGetDouble(map: ReadableMap?, key: String?): Double = safeGetDouble(map, key, 0.0)
|
||||
|
||||
@JvmStatic fun safeGetFloat(map: ReadableMap?, key: String?, fallback: Float): Float =
|
||||
if (map != null && map.hasKey(key!!) && !map.isNull(key)) map.getDouble(key).toFloat() else fallback
|
||||
|
||||
@JvmStatic fun safeGetFloat(map: ReadableMap?, key: String?): Float = safeGetFloat(map, key, 0.0f)
|
||||
|
||||
/**
|
||||
* toStringMap converts a [ReadableMap] into a HashMap.
|
||||
*
|
||||
* @param readableMap The ReadableMap to be conveted.
|
||||
* @return A HashMap containing the data that was in the ReadableMap.
|
||||
* @see 'Adapted from https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java'
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toStringMap(readableMap: ReadableMap?): Map<String, String?>? {
|
||||
if (readableMap == null) return null
|
||||
val iterator = readableMap.keySetIterator()
|
||||
if (!iterator.hasNextKey()) return null
|
||||
val result: MutableMap<String, String?> = HashMap()
|
||||
while (iterator.hasNextKey()) {
|
||||
val key = iterator.nextKey()
|
||||
result[key] = readableMap.getString(key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* toIntMap converts a [ReadableMap] into a HashMap.
|
||||
*
|
||||
* @param readableMap The ReadableMap to be conveted.
|
||||
* @return A HashMap containing the data that was in the ReadableMap.
|
||||
* @see 'Adapted from https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java'
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toIntMap(readableMap: ReadableMap?): Map<String, Int>? {
|
||||
if (readableMap == null) return null
|
||||
val iterator = readableMap.keySetIterator()
|
||||
if (!iterator.hasNextKey()) return null
|
||||
val result: MutableMap<String, Int> = HashMap()
|
||||
while (iterator.hasNextKey()) {
|
||||
val key = iterator.nextKey()
|
||||
result[key] = readableMap.getInt(key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun safeStringEquals(str1: String?, str2: String?): Boolean {
|
||||
if (str1 == null && str2 == null) return true // both are null
|
||||
return if (str1 == null || str2 == null) false else str1 == str2 // only 1 is null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun safeStringArrayEquals(str1: Array<String>?, str2: Array<String>?): Boolean {
|
||||
if (str1 == null && str2 == null) return true // both are null
|
||||
if (str1 == null || str2 == null) return false // only 1 is null
|
||||
if (str1.size != str2.size) return false // only 1 is null
|
||||
for (i in str1.indices) {
|
||||
if (str1[i] == str2[i]) {
|
||||
// standard check
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun safeStringMapEquals(first: Map<String?, String?>?, second: Map<String?, String?>?): Boolean {
|
||||
if (first == null && second == null) return true // both are null
|
||||
if (first == null || second == null) return false // only 1 is null
|
||||
if (first.size != second.size) {
|
||||
return false
|
||||
}
|
||||
for (key in first.keys) {
|
||||
if (!safeStringEquals(first[key], second[key])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.brentvatne.common.api.ResizeMode;
|
||||
|
||||
/**
|
||||
* A {@link FrameLayout} that resizes itself to match a specified aspect ratio.
|
||||
*/
|
||||
public final class AspectRatioFrameLayout extends FrameLayout {
|
||||
|
||||
/**
|
||||
* The {@link FrameLayout} will not resize itself if the fractional difference between its natural
|
||||
* aspect ratio and the requested aspect ratio falls below this threshold.
|
||||
* <p>
|
||||
* This tolerance allows the view to occupy the whole of the screen when the requested aspect
|
||||
* ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce
|
||||
* the number of view layers that need to be composited by the underlying system, which can help
|
||||
* to reduce power consumption.
|
||||
*/
|
||||
private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f;
|
||||
|
||||
private float videoAspectRatio;
|
||||
private @ResizeMode.Mode int resizeMode = ResizeMode.RESIZE_MODE_FIT;
|
||||
|
||||
public AspectRatioFrameLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AspectRatioFrameLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the aspect ratio that this view should satisfy.
|
||||
*
|
||||
* @param widthHeightRatio The width to height ratio.
|
||||
*/
|
||||
public void setAspectRatio(float widthHeightRatio) {
|
||||
if (this.videoAspectRatio != widthHeightRatio) {
|
||||
this.videoAspectRatio = widthHeightRatio;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the aspect ratio that this view should satisfy.
|
||||
*
|
||||
* @return widthHeightRatio The width to height ratio.
|
||||
*/
|
||||
public float getAspectRatio() {
|
||||
return videoAspectRatio;
|
||||
}
|
||||
|
||||
public void invalidateAspectRatio() {
|
||||
videoAspectRatio = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resize mode which can be of value {@link ResizeMode.Mode}
|
||||
*
|
||||
* @param resizeMode The resize mode.
|
||||
*/
|
||||
public void setResizeMode(@ResizeMode.Mode int resizeMode) {
|
||||
if (this.resizeMode != resizeMode) {
|
||||
this.resizeMode = resizeMode;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resize mode which can be of value {@link ResizeMode.Mode}
|
||||
*
|
||||
* @return resizeMode The resize mode.
|
||||
*/
|
||||
public @ResizeMode.Mode int getResizeMode() {
|
||||
return resizeMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (videoAspectRatio == 0) {
|
||||
// Aspect ratio not set.
|
||||
return;
|
||||
}
|
||||
|
||||
int measuredWidth = getMeasuredWidth();
|
||||
int measuredHeight = getMeasuredHeight();
|
||||
int width = measuredWidth;
|
||||
int height = measuredHeight;
|
||||
|
||||
float viewAspectRatio = (float) measuredWidth / measuredHeight;
|
||||
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
|
||||
if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
|
||||
// We're within the allowed tolerance.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (resizeMode) {
|
||||
case ResizeMode.RESIZE_MODE_FIXED_WIDTH:
|
||||
height = (int) (measuredWidth / videoAspectRatio);
|
||||
break;
|
||||
case ResizeMode.RESIZE_MODE_FIXED_HEIGHT:
|
||||
width = (int) (measuredHeight * videoAspectRatio);
|
||||
break;
|
||||
case ResizeMode.RESIZE_MODE_FILL:
|
||||
// Do nothing width and height is the same as the view
|
||||
break;
|
||||
case ResizeMode.RESIZE_MODE_CENTER_CROP:
|
||||
width = (int) (measuredHeight * videoAspectRatio);
|
||||
|
||||
// Scale video if it doesn't fill the measuredWidth
|
||||
if (width < measuredWidth) {
|
||||
float scaleFactor = (float) measuredWidth / width;
|
||||
width = (int) (width * scaleFactor);
|
||||
height = (int) (measuredHeight * scaleFactor);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (aspectDeformation > 0) {
|
||||
height = (int) (measuredWidth / videoAspectRatio);
|
||||
} else {
|
||||
width = (int) (measuredHeight * videoAspectRatio);
|
||||
}
|
||||
break;
|
||||
}
|
||||
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
public enum AudioOutput {
|
||||
SPEAKER("speaker", C.STREAM_TYPE_MUSIC),
|
||||
EARPIECE("earpiece", C.STREAM_TYPE_VOICE_CALL);
|
||||
|
||||
private final @C.StreamType int streamType;
|
||||
private final String mName;
|
||||
|
||||
AudioOutput(final String name, @C.StreamType int stream) {
|
||||
mName = name;
|
||||
streamType = stream;
|
||||
}
|
||||
|
||||
public static AudioOutput get(String name) {
|
||||
for (AudioOutput d : values()) {
|
||||
if (d.mName.equalsIgnoreCase(name))
|
||||
return d;
|
||||
}
|
||||
return SPEAKER;
|
||||
}
|
||||
|
||||
public int getStreamType() {
|
||||
return streamType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "(" + this.mName + ", " + streamType + ")";
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DefaultDataSource;
|
||||
import androidx.media3.datasource.HttpDataSource;
|
||||
import androidx.media3.datasource.okhttp.OkHttpDataSource;
|
||||
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.network.CookieJarContainer;
|
||||
import com.facebook.react.modules.network.ForwardingCookieHandler;
|
||||
import com.facebook.react.modules.network.OkHttpClientProvider;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.JavaNetCookieJar;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class DataSourceUtil {
|
||||
|
||||
private DataSourceUtil() {
|
||||
}
|
||||
|
||||
private static DataSource.Factory rawDataSourceFactory = null;
|
||||
private static DataSource.Factory defaultDataSourceFactory = null;
|
||||
private static HttpDataSource.Factory defaultHttpDataSourceFactory = null;
|
||||
private static String userAgent = null;
|
||||
|
||||
public static void setUserAgent(String userAgent) {
|
||||
DataSourceUtil.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public static String getUserAgent(ReactContext context) {
|
||||
if (userAgent == null) {
|
||||
userAgent = Util.getUserAgent(context, "ReactNativeVideo");
|
||||
}
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public static DataSource.Factory getRawDataSourceFactory(ReactContext context) {
|
||||
if (rawDataSourceFactory == null) {
|
||||
rawDataSourceFactory = buildRawDataSourceFactory(context);
|
||||
}
|
||||
return rawDataSourceFactory;
|
||||
}
|
||||
|
||||
public static void setRawDataSourceFactory(DataSource.Factory factory) {
|
||||
DataSourceUtil.rawDataSourceFactory = factory;
|
||||
}
|
||||
|
||||
|
||||
public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
|
||||
if (defaultDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) {
|
||||
defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter, requestHeaders);
|
||||
}
|
||||
return defaultDataSourceFactory;
|
||||
}
|
||||
|
||||
public static void setDefaultDataSourceFactory(DataSource.Factory factory) {
|
||||
DataSourceUtil.defaultDataSourceFactory = factory;
|
||||
}
|
||||
|
||||
public static HttpDataSource.Factory getDefaultHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
|
||||
if (defaultHttpDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) {
|
||||
defaultHttpDataSourceFactory = buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders);
|
||||
}
|
||||
return defaultHttpDataSourceFactory;
|
||||
}
|
||||
|
||||
public static void setDefaultHttpDataSourceFactory(HttpDataSource.Factory factory) {
|
||||
DataSourceUtil.defaultHttpDataSourceFactory = factory;
|
||||
}
|
||||
|
||||
private static DataSource.Factory buildRawDataSourceFactory(ReactContext context) {
|
||||
return new RawResourceDataSourceFactory(context.getApplicationContext());
|
||||
}
|
||||
|
||||
private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
|
||||
return new DefaultDataSource.Factory(context, buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders));
|
||||
}
|
||||
|
||||
private static HttpDataSource.Factory buildHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
|
||||
OkHttpClient client = OkHttpClientProvider.getOkHttpClient();
|
||||
CookieJarContainer container = (CookieJarContainer) client.cookieJar();
|
||||
ForwardingCookieHandler handler = new ForwardingCookieHandler(context);
|
||||
container.setCookieJar(new JavaNetCookieJar(handler));
|
||||
OkHttpDataSource.Factory okHttpDataSourceFactory = new OkHttpDataSource.Factory((Call.Factory) client)
|
||||
.setTransferListener(bandwidthMeter);
|
||||
|
||||
if (requestHeaders != null) {
|
||||
okHttpDataSourceFactory.setDefaultRequestProperties(requestHeaders);
|
||||
if (!requestHeaders.containsKey("User-Agent")) {
|
||||
okHttpDataSourceFactory.setUserAgent(getUserAgent(context));
|
||||
}
|
||||
} else {
|
||||
okHttpDataSourceFactory.setUserAgent(getUserAgent(context));
|
||||
}
|
||||
|
||||
return okHttpDataSourceFactory;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
|
||||
public class DefaultReactExoplayerConfig implements ReactExoplayerConfig {
|
||||
|
||||
private final DefaultBandwidthMeter bandwidthMeter;
|
||||
private boolean disableDisconnectError = false;
|
||||
|
||||
public DefaultReactExoplayerConfig(Context context) {
|
||||
this.bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
|
||||
}
|
||||
|
||||
public LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount) {
|
||||
if (this.disableDisconnectError) {
|
||||
// Use custom error handling policy to prevent throwing an error when losing network connection
|
||||
return new ReactExoplayerLoadErrorHandlingPolicy(minLoadRetryCount);
|
||||
}
|
||||
return new DefaultLoadErrorHandlingPolicy(minLoadRetryCount);
|
||||
}
|
||||
|
||||
public void setDisableDisconnectError(boolean disableDisconnectError) {
|
||||
this.disableDisconnectError = disableDisconnectError;
|
||||
}
|
||||
|
||||
public boolean getDisableDisconnectError() {
|
||||
return this.disableDisconnectError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultBandwidthMeter getBandwidthMeter() {
|
||||
return bandwidthMeter;
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.media3.common.AdViewProvider;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.PlaybackParameters;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.VideoSize;
|
||||
import androidx.media3.common.text.Cue;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray;
|
||||
import androidx.media3.ui.SubtitleView;
|
||||
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.brentvatne.common.api.ResizeMode;
|
||||
import com.brentvatne.common.api.SubtitleStyle;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
||||
|
||||
private View surfaceView;
|
||||
private final View shutterView;
|
||||
private final SubtitleView subtitleLayout;
|
||||
private final AspectRatioFrameLayout layout;
|
||||
private final ComponentListener componentListener;
|
||||
private ExoPlayer player;
|
||||
private Context context;
|
||||
private ViewGroup.LayoutParams layoutParams;
|
||||
private final FrameLayout adOverlayFrameLayout;
|
||||
|
||||
private boolean useTextureView = true;
|
||||
private boolean useSecureView = false;
|
||||
private boolean hideShutterView = false;
|
||||
|
||||
public ExoPlayerView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ExoPlayerView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
this.context = context;
|
||||
|
||||
layoutParams = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
|
||||
componentListener = new ComponentListener();
|
||||
|
||||
FrameLayout.LayoutParams aspectRatioParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT);
|
||||
aspectRatioParams.gravity = Gravity.CENTER;
|
||||
layout = new AspectRatioFrameLayout(context);
|
||||
layout.setLayoutParams(aspectRatioParams);
|
||||
|
||||
shutterView = new View(getContext());
|
||||
shutterView.setLayoutParams(layoutParams);
|
||||
shutterView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black));
|
||||
|
||||
subtitleLayout = new SubtitleView(context);
|
||||
subtitleLayout.setLayoutParams(layoutParams);
|
||||
subtitleLayout.setUserDefaultStyle();
|
||||
subtitleLayout.setUserDefaultTextSize();
|
||||
|
||||
updateSurfaceView();
|
||||
|
||||
adOverlayFrameLayout = new FrameLayout(context);
|
||||
|
||||
layout.addView(shutterView, 1, layoutParams);
|
||||
layout.addView(subtitleLayout, 2, layoutParams);
|
||||
layout.addView(adOverlayFrameLayout, 3, layoutParams);
|
||||
|
||||
addViewInLayout(layout, 0, aspectRatioParams);
|
||||
}
|
||||
|
||||
private void clearVideoView() {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
player.clearVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
player.clearVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
}
|
||||
|
||||
private void setVideoView() {
|
||||
if (surfaceView instanceof TextureView) {
|
||||
player.setVideoTextureView((TextureView) surfaceView);
|
||||
} else if (surfaceView instanceof SurfaceView) {
|
||||
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return player != null && player.isPlaying();
|
||||
}
|
||||
|
||||
public void setSubtitleStyle(SubtitleStyle style) {
|
||||
// ensure we reset subtile style before reapplying it
|
||||
subtitleLayout.setUserDefaultStyle();
|
||||
subtitleLayout.setUserDefaultTextSize();
|
||||
|
||||
if (style.getFontSize() > 0) {
|
||||
subtitleLayout.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, style.getFontSize());
|
||||
}
|
||||
subtitleLayout.setPadding(style.getPaddingLeft(), style.getPaddingTop(), style.getPaddingRight(), style.getPaddingBottom());
|
||||
if (style.getOpacity() != 0) {
|
||||
subtitleLayout.setAlpha(style.getOpacity());
|
||||
subtitleLayout.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
subtitleLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setShutterColor(Integer color) {
|
||||
shutterView.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
private void updateSurfaceView() {
|
||||
View view;
|
||||
if (!useTextureView || useSecureView) {
|
||||
view = new SurfaceView(context);
|
||||
if (useSecureView) {
|
||||
((SurfaceView)view).setSecure(true);
|
||||
}
|
||||
} else {
|
||||
view = new TextureView(context);
|
||||
// Support opacity properly:
|
||||
((TextureView) view).setOpaque(false);
|
||||
}
|
||||
view.setLayoutParams(layoutParams);
|
||||
|
||||
surfaceView = view;
|
||||
if (layout.getChildAt(0) != null) {
|
||||
layout.removeViewAt(0);
|
||||
}
|
||||
layout.addView(surfaceView, 0, layoutParams);
|
||||
|
||||
if (this.player != null) {
|
||||
setVideoView();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShutterViewVisibility() {
|
||||
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
super.requestLayout();
|
||||
post(measureAndLayout);
|
||||
}
|
||||
|
||||
// AdsLoader.AdViewProvider implementation.
|
||||
|
||||
@Override
|
||||
public ViewGroup getAdViewGroup() {
|
||||
return Assertions.checkNotNull(adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ExoPlayer} to use. The {@link ExoPlayer#addListener} method of the
|
||||
* player will be called and previous
|
||||
* assignments are overridden.
|
||||
*
|
||||
* @param player The {@link ExoPlayer} to use.
|
||||
*/
|
||||
public void setPlayer(ExoPlayer player) {
|
||||
if (this.player == player) {
|
||||
return;
|
||||
}
|
||||
if (this.player != null) {
|
||||
this.player.removeListener(componentListener);
|
||||
clearVideoView();
|
||||
}
|
||||
this.player = player;
|
||||
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
||||
if (player != null) {
|
||||
setVideoView();
|
||||
player.addListener(componentListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resize mode which can be of value {@link ResizeMode.Mode}
|
||||
*
|
||||
* @param resizeMode The resize mode.
|
||||
*/
|
||||
public void setResizeMode(@ResizeMode.Mode int resizeMode) {
|
||||
if (layout.getResizeMode() != resizeMode) {
|
||||
layout.setResizeMode(resizeMode);
|
||||
post(measureAndLayout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view onto which video is rendered. This is either a {@link SurfaceView} (default)
|
||||
* or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true.
|
||||
*
|
||||
* @return either a {@link SurfaceView} or a {@link TextureView}.
|
||||
*/
|
||||
public View getVideoSurfaceView() {
|
||||
return surfaceView;
|
||||
}
|
||||
|
||||
public void setUseTextureView(boolean useTextureView) {
|
||||
if (useTextureView != this.useTextureView) {
|
||||
this.useTextureView = useTextureView;
|
||||
updateSurfaceView();
|
||||
}
|
||||
}
|
||||
|
||||
public void useSecureView(boolean useSecureView) {
|
||||
if (useSecureView != this.useSecureView) {
|
||||
this.useSecureView = useSecureView;
|
||||
updateSurfaceView();
|
||||
}
|
||||
}
|
||||
|
||||
public void setHideShutterView(boolean hideShutterView) {
|
||||
this.hideShutterView = hideShutterView;
|
||||
updateShutterViewVisibility();
|
||||
}
|
||||
|
||||
private final Runnable measureAndLayout = () -> {
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||
layout(getLeft(), getTop(), getRight(), getBottom());
|
||||
};
|
||||
|
||||
private void updateForCurrentTrackSelections() {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
TrackSelectionArray selections = player.getCurrentTrackSelections();
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) {
|
||||
// Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in
|
||||
// onRenderedFirstFrame().
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Video disabled so the shutter must be closed.
|
||||
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
public void invalidateAspectRatio() {
|
||||
// Resetting aspect ratio will force layout refresh on next video size changed
|
||||
layout.invalidateAspectRatio();
|
||||
}
|
||||
|
||||
private final class ComponentListener implements Player.Listener {
|
||||
|
||||
@Override
|
||||
public void onCues(List<Cue> cues) {
|
||||
subtitleLayout.setCues(cues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(VideoSize videoSize) {
|
||||
boolean isInitialRatio = layout.getAspectRatio() == 0;
|
||||
layout.setAspectRatio(videoSize.height == 0 ? 1 : (videoSize.width * videoSize.pixelWidthHeightRatio) / videoSize.height);
|
||||
|
||||
// React native workaround for measuring and layout on initial load.
|
||||
if (isInitialRatio) {
|
||||
post(measureAndLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame() {
|
||||
shutterView.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(Tracks tracks) {
|
||||
updateForCurrentTrackSelections();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.media3.ui.LegacyPlayerControlView;
|
||||
|
||||
import com.brentvatne.common.toolbox.DebugLog;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
public class FullScreenPlayerView extends Dialog {
|
||||
private final LegacyPlayerControlView playerControlView;
|
||||
private final ExoPlayerView exoPlayerView;
|
||||
private final ReactExoplayerView reactExoplayerView;
|
||||
private ViewGroup parent;
|
||||
private final FrameLayout containerView;
|
||||
private final OnBackPressedCallback onBackPressedCallback;
|
||||
private final Handler mKeepScreenOnHandler;
|
||||
private final Runnable mKeepScreenOnUpdater;
|
||||
|
||||
private static class KeepScreenOnUpdater implements Runnable {
|
||||
private final static long UPDATE_KEEP_SCREEN_ON_FLAG_MS = 200;
|
||||
private final WeakReference<FullScreenPlayerView> mFullscreenPlayer;
|
||||
|
||||
KeepScreenOnUpdater(FullScreenPlayerView player) {
|
||||
mFullscreenPlayer = new WeakReference<>(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
FullScreenPlayerView fullscreenVideoPlayer = mFullscreenPlayer.get();
|
||||
if (fullscreenVideoPlayer != null) {
|
||||
final Window window = fullscreenVideoPlayer.getWindow();
|
||||
if (window != null) {
|
||||
boolean isPlaying = fullscreenVideoPlayer.exoPlayerView.isPlaying();
|
||||
if (isPlaying) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
fullscreenVideoPlayer.mKeepScreenOnHandler.postDelayed(this, UPDATE_KEEP_SCREEN_ON_FLAG_MS);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
DebugLog.e("ExoPlayer Exception", "Failed to flag FLAG_KEEP_SCREEN_ON on fullscreeen.");
|
||||
DebugLog.e("ExoPlayer Exception", ex.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FullScreenPlayerView(Context context, ExoPlayerView exoPlayerView, ReactExoplayerView reactExoplayerView, LegacyPlayerControlView playerControlView, OnBackPressedCallback onBackPressedCallback) {
|
||||
super(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||
this.playerControlView = playerControlView;
|
||||
this.exoPlayerView = exoPlayerView;
|
||||
this.reactExoplayerView = reactExoplayerView;
|
||||
this.onBackPressedCallback = onBackPressedCallback;
|
||||
containerView = new FrameLayout(context);
|
||||
setContentView(containerView, generateDefaultLayoutParams());
|
||||
|
||||
mKeepScreenOnUpdater = new KeepScreenOnUpdater(this);
|
||||
mKeepScreenOnHandler = new Handler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
onBackPressedCallback.handleOnBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
parent = (FrameLayout)(exoPlayerView.getParent());
|
||||
|
||||
parent.removeView(exoPlayerView);
|
||||
containerView.addView(exoPlayerView, generateDefaultLayoutParams());
|
||||
|
||||
if (playerControlView != null) {
|
||||
ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen);
|
||||
imageButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_exit);
|
||||
imageButton.setContentDescription(getContext().getString(androidx.media3.ui.R.string.exo_controls_fullscreen_exit_description));
|
||||
parent.removeView(playerControlView);
|
||||
containerView.addView(playerControlView, generateDefaultLayoutParams());
|
||||
}
|
||||
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mKeepScreenOnHandler.removeCallbacks(mKeepScreenOnUpdater);
|
||||
containerView.removeView(exoPlayerView);
|
||||
parent.addView(exoPlayerView, generateDefaultLayoutParams());
|
||||
|
||||
if (playerControlView != null) {
|
||||
ImageButton imageButton = playerControlView.findViewById(com.brentvatne.react.R.id.exo_fullscreen);
|
||||
imageButton.setImageResource(androidx.media3.ui.R.drawable.exo_icon_fullscreen_enter);
|
||||
imageButton.setContentDescription(getContext().getString(androidx.media3.ui.R.string.exo_controls_fullscreen_enter_description));
|
||||
containerView.removeView(playerControlView);
|
||||
parent.addView(playerControlView, generateDefaultLayoutParams());
|
||||
}
|
||||
|
||||
parent.requestLayout();
|
||||
parent = null;
|
||||
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (reactExoplayerView.getPreventsDisplaySleepDuringVideoPlayback()) {
|
||||
mKeepScreenOnHandler.post(mKeepScreenOnUpdater);
|
||||
}
|
||||
}
|
||||
|
||||
private FrameLayout.LayoutParams generateDefaultLayoutParams() {
|
||||
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
);
|
||||
layoutParams.setMargins(0, 0, 0, 0);
|
||||
return layoutParams;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.RawResourceDataSource;
|
||||
|
||||
class RawResourceDataSourceFactory implements DataSource.Factory {
|
||||
|
||||
private final Context context;
|
||||
|
||||
RawResourceDataSourceFactory(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource createDataSource() {
|
||||
return new RawResourceDataSource(context);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
|
||||
|
||||
/**
|
||||
* Extension points to configure the Exoplayer instance
|
||||
*/
|
||||
public interface ReactExoplayerConfig {
|
||||
LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount);
|
||||
|
||||
void setDisableDisconnectError(boolean disableDisconnectError);
|
||||
boolean getDisableDisconnectError();
|
||||
|
||||
DefaultBandwidthMeter getBandwidthMeter();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
|
||||
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
|
||||
|
||||
public final class ReactExoplayerLoadErrorHandlingPolicy extends DefaultLoadErrorHandlingPolicy {
|
||||
private final int minLoadRetryCount;
|
||||
|
||||
public ReactExoplayerLoadErrorHandlingPolicy(int minLoadRetryCount) {
|
||||
super(minLoadRetryCount);
|
||||
this.minLoadRetryCount = minLoadRetryCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
|
||||
String errorMessage = loadErrorInfo.exception.getMessage();
|
||||
|
||||
if (
|
||||
loadErrorInfo.exception instanceof HttpDataSourceException &&
|
||||
errorMessage != null && (errorMessage.equals("Unable to connect") || errorMessage.equals("Software caused connection abort"))
|
||||
) {
|
||||
// Capture the error we get when there is no network connectivity and keep retrying it
|
||||
return 1000; // Retry every second
|
||||
} else if(loadErrorInfo.errorCount < this.minLoadRetryCount) {
|
||||
return Math.min((loadErrorInfo.errorCount - 1) * 1000, 5000); // Default timeout handling
|
||||
} else {
|
||||
return C.TIME_UNSET; // Done retrying and will return the error immediately
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinimumLoadableRetryCount(int dataType) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,456 +0,0 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.RawResourceDataSource;
|
||||
import androidx.media3.exoplayer.DefaultLoadControl;
|
||||
|
||||
import com.brentvatne.common.api.ResizeMode;
|
||||
import com.brentvatne.common.api.SubtitleStyle;
|
||||
import com.brentvatne.common.react.VideoEventEmitter;
|
||||
import com.brentvatne.common.toolbox.DebugLog;
|
||||
import com.brentvatne.common.toolbox.ReactBridgeUtils;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerView> {
|
||||
|
||||
private static final String REACT_CLASS = "RCTVideo";
|
||||
private static final String PROP_SRC = "src";
|
||||
private static final String PROP_SRC_URI = "uri";
|
||||
private static final String PROP_SRC_START_POSITION = "startPosition";
|
||||
private static final String PROP_SRC_CROP_START = "cropStart";
|
||||
private static final String PROP_SRC_CROP_END = "cropEnd";
|
||||
private static final String PROP_AD_TAG_URL = "adTagUrl";
|
||||
private static final String PROP_SRC_TYPE = "type";
|
||||
private static final String PROP_DRM = "drm";
|
||||
private static final String PROP_DRM_TYPE = "type";
|
||||
private static final String PROP_DRM_LICENSESERVER = "licenseServer";
|
||||
private static final String PROP_DRM_HEADERS = "headers";
|
||||
private static final String PROP_SRC_HEADERS = "requestHeaders";
|
||||
private static final String PROP_RESIZE_MODE = "resizeMode";
|
||||
private static final String PROP_REPEAT = "repeat";
|
||||
private static final String PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack";
|
||||
private static final String PROP_SELECTED_AUDIO_TRACK_TYPE = "type";
|
||||
private static final String PROP_SELECTED_AUDIO_TRACK_VALUE = "value";
|
||||
private static final String PROP_SELECTED_TEXT_TRACK = "selectedTextTrack";
|
||||
private static final String PROP_SELECTED_TEXT_TRACK_TYPE = "type";
|
||||
private static final String PROP_SELECTED_TEXT_TRACK_VALUE = "value";
|
||||
private static final String PROP_TEXT_TRACKS = "textTracks";
|
||||
private static final String PROP_PAUSED = "paused";
|
||||
private static final String PROP_MUTED = "muted";
|
||||
private static final String PROP_AUDIO_OUTPUT = "audioOutput";
|
||||
private static final String PROP_VOLUME = "volume";
|
||||
private static final String PROP_BACK_BUFFER_DURATION_MS = "backBufferDurationMs";
|
||||
private static final String PROP_BUFFER_CONFIG = "bufferConfig";
|
||||
private static final String PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs";
|
||||
private static final String PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs";
|
||||
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs";
|
||||
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs";
|
||||
private static final String PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT = "maxHeapAllocationPercent";
|
||||
private static final String PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT = "minBackBufferMemoryReservePercent";
|
||||
private static final String PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT = "minBufferMemoryReservePercent";
|
||||
private static final String PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = "preventsDisplaySleepDuringVideoPlayback";
|
||||
private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
|
||||
private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth";
|
||||
private static final String PROP_SEEK = "seek";
|
||||
private static final String PROP_RATE = "rate";
|
||||
private static final String PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount";
|
||||
private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate";
|
||||
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
|
||||
private static final String PROP_CONTENT_START_TIME = "contentStartTime";
|
||||
private static final String PROP_DISABLE_FOCUS = "disableFocus";
|
||||
private static final String PROP_DISABLE_BUFFERING = "disableBuffering";
|
||||
private static final String PROP_DISABLE_DISCONNECT_ERROR = "disableDisconnectError";
|
||||
private static final String PROP_FOCUSABLE = "focusable";
|
||||
private static final String PROP_FULLSCREEN = "fullscreen";
|
||||
private static final String PROP_USE_TEXTURE_VIEW = "useTextureView";
|
||||
private static final String PROP_SECURE_VIEW = "useSecureView";
|
||||
private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack";
|
||||
private static final String PROP_SELECTED_VIDEO_TRACK_TYPE = "type";
|
||||
private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value";
|
||||
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
|
||||
private static final String PROP_CONTROLS = "controls";
|
||||
private static final String PROP_SUBTITLE_STYLE = "subtitleStyle";
|
||||
private static final String PROP_SHUTTER_COLOR = "shutterColor";
|
||||
private static final String PROP_DEBUG = "debug";
|
||||
|
||||
private ReactExoplayerConfig config;
|
||||
|
||||
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactExoplayerView createViewInstance(ThemedReactContext themedReactContext) {
|
||||
return new ReactExoplayerView(themedReactContext, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropViewInstance(ReactExoplayerView view) {
|
||||
view.cleanUpResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
||||
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
|
||||
for (String event : VideoEventEmitter.Events) {
|
||||
builder.put(event, MapBuilder.of("registrationName", event));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_DRM)
|
||||
public void setDRM(final ReactExoplayerView videoView, @Nullable ReadableMap drm) {
|
||||
if (drm != null && drm.hasKey(PROP_DRM_TYPE)) {
|
||||
String drmType = ReactBridgeUtils.safeGetString(drm, PROP_DRM_TYPE);
|
||||
String drmLicenseServer = ReactBridgeUtils.safeGetString(drm, PROP_DRM_LICENSESERVER);
|
||||
ReadableArray drmHeadersArray = ReactBridgeUtils.safeGetArray(drm, PROP_DRM_HEADERS);
|
||||
if (drmType != null && drmLicenseServer != null && Util.getDrmUuid(drmType) != null) {
|
||||
UUID drmUUID = Util.getDrmUuid(drmType);
|
||||
videoView.setDrmType(drmUUID);
|
||||
videoView.setDrmLicenseUrl(drmLicenseServer);
|
||||
if (drmHeadersArray != null) {
|
||||
ArrayList<String> drmKeyRequestPropertiesList = new ArrayList<>();
|
||||
for (int i = 0; i < drmHeadersArray.size(); i++) {
|
||||
ReadableMap current = drmHeadersArray.getMap(i);
|
||||
String key = current.hasKey("key") ? current.getString("key") : null;
|
||||
String value = current.hasKey("value") ? current.getString("value") : null;
|
||||
drmKeyRequestPropertiesList.add(key);
|
||||
drmKeyRequestPropertiesList.add(value);
|
||||
}
|
||||
videoView.setDrmLicenseHeader(drmKeyRequestPropertiesList.toArray(new String[0]));
|
||||
}
|
||||
videoView.setUseTextureView(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SRC)
|
||||
public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) {
|
||||
Context context = videoView.getContext().getApplicationContext();
|
||||
String uriString = ReactBridgeUtils.safeGetString(src, PROP_SRC_URI, null);
|
||||
int startPositionMs = ReactBridgeUtils.safeGetInt(src, PROP_SRC_START_POSITION, -1);
|
||||
int cropStartMs = ReactBridgeUtils.safeGetInt(src, PROP_SRC_CROP_START, -1);
|
||||
int cropEndMs = ReactBridgeUtils.safeGetInt(src, PROP_SRC_CROP_END, -1);
|
||||
String extension = ReactBridgeUtils.safeGetString(src, PROP_SRC_TYPE, null);
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
ReadableArray propSrcHeadersArray = ReactBridgeUtils.safeGetArray(src, PROP_SRC_HEADERS);
|
||||
if (propSrcHeadersArray != null) {
|
||||
if (propSrcHeadersArray.size() > 0) {
|
||||
for (int i = 0; i < propSrcHeadersArray.size(); i++) {
|
||||
ReadableMap current = propSrcHeadersArray.getMap(i);
|
||||
String key = current.hasKey("key") ? current.getString("key") : null;
|
||||
String value = current.hasKey("value") ? current.getString("value") : null;
|
||||
if (key != null && value != null) {
|
||||
headers.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(uriString)) {
|
||||
videoView.clearSrc();
|
||||
return;
|
||||
}
|
||||
|
||||
if (startsWithValidScheme(uriString)) {
|
||||
Uri srcUri = Uri.parse(uriString);
|
||||
|
||||
if (srcUri != null) {
|
||||
videoView.setSrc(srcUri, startPositionMs, cropStartMs, cropEndMs, extension, headers);
|
||||
}
|
||||
} else {
|
||||
int identifier = context.getResources().getIdentifier(
|
||||
uriString,
|
||||
"drawable",
|
||||
context.getPackageName()
|
||||
);
|
||||
if (identifier == 0) {
|
||||
identifier = context.getResources().getIdentifier(
|
||||
uriString,
|
||||
"raw",
|
||||
context.getPackageName()
|
||||
);
|
||||
}
|
||||
if (identifier > 0) {
|
||||
Uri srcUri = RawResourceDataSource.buildRawResourceUri(identifier);
|
||||
if (srcUri != null) {
|
||||
videoView.setRawSrc(srcUri, extension);
|
||||
}
|
||||
} else {
|
||||
videoView.clearSrc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_AD_TAG_URL)
|
||||
public void setAdTagUrl(final ReactExoplayerView videoView, final String uriString) {
|
||||
if (TextUtils.isEmpty(uriString)) {
|
||||
videoView.setAdTagUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Uri adTagUrl = Uri.parse(uriString);
|
||||
|
||||
videoView.setAdTagUrl(adTagUrl);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_RESIZE_MODE)
|
||||
public void setResizeMode(final ReactExoplayerView videoView, final String resizeMode) {
|
||||
switch (resizeMode) {
|
||||
case "none":
|
||||
case "contain":
|
||||
videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FIT);
|
||||
break;
|
||||
case "cover":
|
||||
videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_CENTER_CROP);
|
||||
break;
|
||||
case "stretch":
|
||||
videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FILL);
|
||||
break;
|
||||
default:
|
||||
DebugLog.w("ExoPlayer Warning", "Unsupported resize mode: " + resizeMode + " - falling back to fit");
|
||||
videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_REPEAT, defaultBoolean = false)
|
||||
public void setRepeat(final ReactExoplayerView videoView, final boolean repeat) {
|
||||
videoView.setRepeatModifier(repeat);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK, defaultBoolean = false)
|
||||
public void setPreventsDisplaySleepDuringVideoPlayback(final ReactExoplayerView videoView, final boolean preventsSleep) {
|
||||
videoView.setPreventsDisplaySleepDuringVideoPlayback(preventsSleep);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SELECTED_VIDEO_TRACK)
|
||||
public void setSelectedVideoTrack(final ReactExoplayerView videoView,
|
||||
@Nullable ReadableMap selectedVideoTrack) {
|
||||
String typeString = null;
|
||||
String value = null;
|
||||
if (selectedVideoTrack != null) {
|
||||
typeString = ReactBridgeUtils.safeGetString(selectedVideoTrack, PROP_SELECTED_VIDEO_TRACK_TYPE);
|
||||
value = ReactBridgeUtils.safeGetString(selectedVideoTrack, PROP_SELECTED_VIDEO_TRACK_VALUE);
|
||||
}
|
||||
videoView.setSelectedVideoTrack(typeString, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SELECTED_AUDIO_TRACK)
|
||||
public void setSelectedAudioTrack(final ReactExoplayerView videoView,
|
||||
@Nullable ReadableMap selectedAudioTrack) {
|
||||
String typeString = null;
|
||||
String value = null;
|
||||
if (selectedAudioTrack != null) {
|
||||
typeString = ReactBridgeUtils.safeGetString(selectedAudioTrack, PROP_SELECTED_AUDIO_TRACK_TYPE);
|
||||
value = ReactBridgeUtils.safeGetString(selectedAudioTrack, PROP_SELECTED_AUDIO_TRACK_VALUE);
|
||||
}
|
||||
videoView.setSelectedAudioTrack(typeString, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SELECTED_TEXT_TRACK)
|
||||
public void setSelectedTextTrack(final ReactExoplayerView videoView,
|
||||
@Nullable ReadableMap selectedTextTrack) {
|
||||
String typeString = null;
|
||||
String value = null;
|
||||
if (selectedTextTrack != null) {
|
||||
typeString = ReactBridgeUtils.safeGetString(selectedTextTrack, PROP_SELECTED_TEXT_TRACK_TYPE);
|
||||
value = ReactBridgeUtils.safeGetString(selectedTextTrack, PROP_SELECTED_TEXT_TRACK_VALUE);
|
||||
}
|
||||
videoView.setSelectedTextTrack(typeString, value);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_TEXT_TRACKS)
|
||||
public void setPropTextTracks(final ReactExoplayerView videoView,
|
||||
@Nullable ReadableArray textTracks) {
|
||||
videoView.setTextTracks(textTracks);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_PAUSED, defaultBoolean = false)
|
||||
public void setPaused(final ReactExoplayerView videoView, final boolean paused) {
|
||||
videoView.setPausedModifier(paused);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_MUTED, defaultBoolean = false)
|
||||
public void setMuted(final ReactExoplayerView videoView, final boolean muted) {
|
||||
videoView.setMutedModifier(muted);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_AUDIO_OUTPUT)
|
||||
public void setAudioOutput(final ReactExoplayerView videoView, final String audioOutput) {
|
||||
videoView.setAudioOutput(AudioOutput.get(audioOutput));
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f)
|
||||
public void setVolume(final ReactExoplayerView videoView, final float volume) {
|
||||
videoView.setVolumeModifier(volume);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_PROGRESS_UPDATE_INTERVAL, defaultFloat = 250.0f)
|
||||
public void setProgressUpdateInterval(final ReactExoplayerView videoView, final float progressUpdateInterval) {
|
||||
videoView.setProgressUpdateInterval(progressUpdateInterval);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_REPORT_BANDWIDTH, defaultBoolean = false)
|
||||
public void setReportBandwidth(final ReactExoplayerView videoView, final boolean reportBandwidth) {
|
||||
videoView.setReportBandwidth(reportBandwidth);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SEEK)
|
||||
public void setSeek(final ReactExoplayerView videoView, final float seek) {
|
||||
videoView.seekTo(Math.round(seek * 1000f));
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_RATE)
|
||||
public void setRate(final ReactExoplayerView videoView, final float rate) {
|
||||
videoView.setRateModifier(rate);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_MAXIMUM_BIT_RATE)
|
||||
public void setMaxBitRate(final ReactExoplayerView videoView, final int maxBitRate) {
|
||||
videoView.setMaxBitRateModifier(maxBitRate);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_MIN_LOAD_RETRY_COUNT)
|
||||
public void minLoadRetryCount(final ReactExoplayerView videoView, final int minLoadRetryCount) {
|
||||
videoView.setMinLoadRetryCountModifier(minLoadRetryCount);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false)
|
||||
public void setPlayInBackground(final ReactExoplayerView videoView, final boolean playInBackground) {
|
||||
videoView.setPlayInBackground(playInBackground);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_DISABLE_FOCUS, defaultBoolean = false)
|
||||
public void setDisableFocus(final ReactExoplayerView videoView, final boolean disableFocus) {
|
||||
videoView.setDisableFocus(disableFocus);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_FOCUSABLE, defaultBoolean = true)
|
||||
public void setFocusable(final ReactExoplayerView videoView, final boolean focusable) {
|
||||
videoView.setFocusable(focusable);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_BACK_BUFFER_DURATION_MS, defaultInt = 0)
|
||||
public void setBackBufferDurationMs(final ReactExoplayerView videoView, final int backBufferDurationMs) {
|
||||
videoView.setBackBufferDurationMs(backBufferDurationMs);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_CONTENT_START_TIME, defaultInt = -1)
|
||||
public void setContentStartTime(final ReactExoplayerView videoView, final int contentStartTime) {
|
||||
videoView.setContentStartTime(contentStartTime);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_DISABLE_BUFFERING, defaultBoolean = false)
|
||||
public void setDisableBuffering(final ReactExoplayerView videoView, final boolean disableBuffering) {
|
||||
videoView.setDisableBuffering(disableBuffering);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_DISABLE_DISCONNECT_ERROR, defaultBoolean = false)
|
||||
public void setDisableDisconnectError(final ReactExoplayerView videoView, final boolean disableDisconnectError) {
|
||||
videoView.setDisableDisconnectError(disableDisconnectError);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false)
|
||||
public void setFullscreen(final ReactExoplayerView videoView, final boolean fullscreen) {
|
||||
videoView.setFullscreen(fullscreen);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_USE_TEXTURE_VIEW, defaultBoolean = true)
|
||||
public void setUseTextureView(final ReactExoplayerView videoView, final boolean useTextureView) {
|
||||
videoView.setUseTextureView(useTextureView);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SECURE_VIEW, defaultBoolean = true)
|
||||
public void useSecureView(final ReactExoplayerView videoView, final boolean useSecureView) {
|
||||
videoView.useSecureView(useSecureView);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_HIDE_SHUTTER_VIEW, defaultBoolean = false)
|
||||
public void setHideShutterView(final ReactExoplayerView videoView, final boolean hideShutterView) {
|
||||
videoView.setHideShutterView(hideShutterView);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_CONTROLS, defaultBoolean = false)
|
||||
public void setControls(final ReactExoplayerView videoView, final boolean controls) {
|
||||
videoView.setControls(controls);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SUBTITLE_STYLE)
|
||||
public void setSubtitleStyle(final ReactExoplayerView videoView, @Nullable final ReadableMap src) {
|
||||
videoView.setSubtitleStyle(SubtitleStyle.parse(src));
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_SHUTTER_COLOR, customType = "Color")
|
||||
public void setShutterColor(final ReactExoplayerView videoView, final Integer color) {
|
||||
videoView.setShutterColor(color == null ? Color.BLACK : color);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_BUFFER_CONFIG)
|
||||
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
|
||||
int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
||||
int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||
int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
||||
int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
||||
double maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT;
|
||||
double minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE;
|
||||
double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE;
|
||||
|
||||
if (bufferConfig != null) {
|
||||
minBufferMs = ReactBridgeUtils.safeGetInt(bufferConfig, PROP_BUFFER_CONFIG_MIN_BUFFER_MS, minBufferMs);
|
||||
maxBufferMs = ReactBridgeUtils.safeGetInt(bufferConfig, PROP_BUFFER_CONFIG_MAX_BUFFER_MS, maxBufferMs);
|
||||
bufferForPlaybackMs = ReactBridgeUtils.safeGetInt(bufferConfig, PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS, bufferForPlaybackMs);
|
||||
bufferForPlaybackAfterRebufferMs = ReactBridgeUtils.safeGetInt(bufferConfig, PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, bufferForPlaybackAfterRebufferMs);
|
||||
maxHeapAllocationPercent = ReactBridgeUtils.safeGetDouble(bufferConfig, PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT, maxHeapAllocationPercent);
|
||||
minBackBufferMemoryReservePercent = ReactBridgeUtils.safeGetDouble(bufferConfig, PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT, minBackBufferMemoryReservePercent);
|
||||
minBufferMemoryReservePercent = ReactBridgeUtils.safeGetDouble(bufferConfig, PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT, minBufferMemoryReservePercent);
|
||||
videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, maxHeapAllocationPercent, minBackBufferMemoryReservePercent, minBufferMemoryReservePercent);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_DEBUG, defaultBoolean = false)
|
||||
public void setDebug(final ReactExoplayerView videoView,
|
||||
@Nullable final ReadableMap debugConfig) {
|
||||
boolean enableDebug = ReactBridgeUtils.safeGetBool(debugConfig, "enable", false);
|
||||
boolean enableThreadDebug = ReactBridgeUtils.safeGetBool(debugConfig, "thread", false);
|
||||
if (enableDebug) {
|
||||
DebugLog.setConfig(Log.VERBOSE, enableThreadDebug);
|
||||
} else {
|
||||
DebugLog.setConfig(Log.WARN, enableThreadDebug);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startsWithValidScheme(String uriString) {
|
||||
String lowerCaseUri = uriString.toLowerCase();
|
||||
return lowerCaseUri.startsWith("http://")
|
||||
|| lowerCaseUri.startsWith("https://")
|
||||
|| lowerCaseUri.startsWith("content://")
|
||||
|| lowerCaseUri.startsWith("file://")
|
||||
|| lowerCaseUri.startsWith("asset://");
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.brentvatne.react;
|
||||
|
||||
import com.brentvatne.exoplayer.DefaultReactExoplayerConfig;
|
||||
import com.brentvatne.exoplayer.ReactExoplayerConfig;
|
||||
import com.brentvatne.exoplayer.ReactExoplayerViewManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ReactVideoPackage implements ReactPackage {
|
||||
|
||||
private ReactExoplayerConfig config;
|
||||
|
||||
public ReactVideoPackage() {
|
||||
}
|
||||
|
||||
public ReactVideoPackage(ReactExoplayerConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<NativeModule>();
|
||||
|
||||
modules.add(new VideoDecoderPropertiesModule(reactContext));
|
||||
modules.add(new VideoManagerModule(reactContext));
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
// Deprecated RN 0.47
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
if (config == null) {
|
||||
config = new DefaultReactExoplayerConfig(reactContext);
|
||||
}
|
||||
return Collections.singletonList(new ReactExoplayerViewManager(config));
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.brentvatne.react;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaDrm;
|
||||
import android.media.MediaFormat;
|
||||
import android.media.UnsupportedSchemeException;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
public class VideoDecoderPropertiesModule extends ReactContextBaseJavaModule {
|
||||
|
||||
ReactApplicationContext reactContext;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "VideoDecoderProperties";
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
@ReactMethod
|
||||
public void getWidevineLevel(Promise p) {
|
||||
int widevineLevel = 0;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
p.resolve(widevineLevel);
|
||||
return;
|
||||
}
|
||||
final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
|
||||
final String WIDEVINE_SECURITY_LEVEL_1 = "L1";
|
||||
final String WIDEVINE_SECURITY_LEVEL_2 = "L2";
|
||||
final String WIDEVINE_SECURITY_LEVEL_3 = "L3";
|
||||
final String SECURITY_LEVEL_PROPERTY = "securityLevel";
|
||||
|
||||
String securityProperty = null;
|
||||
try {
|
||||
MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID);
|
||||
securityProperty = mediaDrm.getPropertyString(SECURITY_LEVEL_PROPERTY);
|
||||
} catch (UnsupportedSchemeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (securityProperty == null) {
|
||||
p.resolve(widevineLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (securityProperty) {
|
||||
case WIDEVINE_SECURITY_LEVEL_1: {
|
||||
widevineLevel = 1;
|
||||
break;
|
||||
}
|
||||
case WIDEVINE_SECURITY_LEVEL_2: {
|
||||
widevineLevel = 2;
|
||||
break;
|
||||
}
|
||||
case WIDEVINE_SECURITY_LEVEL_3: {
|
||||
widevineLevel = 3;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// widevineLevel 0
|
||||
break;
|
||||
}
|
||||
}
|
||||
p.resolve(widevineLevel);
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
@ReactMethod
|
||||
public void isCodecSupported(String mimeType, int width, int height, Promise p) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
p.resolve("unsupported");
|
||||
return;
|
||||
}
|
||||
|
||||
MediaCodecList mRegularCodecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
|
||||
String codecName = mRegularCodecs.findDecoderForFormat(format);
|
||||
|
||||
if (codecName == null) {
|
||||
p.resolve("unsupported");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback for android < 10
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
p.resolve("software");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isHardwareAccelerated = false;
|
||||
|
||||
for (MediaCodecInfo codecInfo : mRegularCodecs.getCodecInfos()) {
|
||||
if (codecInfo.getName().equalsIgnoreCase(codecName)) {
|
||||
isHardwareAccelerated = codecInfo.isHardwareAccelerated();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p.resolve(isHardwareAccelerated ? "software" : "hardware");
|
||||
}
|
||||
|
||||
|
||||
@ReactMethod
|
||||
public void isHEVCSupported(Promise p) {
|
||||
isCodecSupported("video/hevc", 1920, 1080, p);
|
||||
}
|
||||
|
||||
public VideoDecoderPropertiesModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
this.reactContext = reactContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.brentvatne.react;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.brentvatne.exoplayer.ReactExoplayerView;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
|
||||
public class VideoManagerModule extends ReactContextBaseJavaModule {
|
||||
private static final String REACT_CLASS = "VideoManager";
|
||||
|
||||
public VideoManagerModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setPlayerPauseState(Boolean paused, int reactTag) {
|
||||
UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
|
||||
uiManager.prependUIBlock(manager -> {
|
||||
View view = manager.resolveView(reactTag);
|
||||
|
||||
if (view instanceof ReactExoplayerView) {
|
||||
ReactExoplayerView videoView = (ReactExoplayerView) view;
|
||||
videoView.setPausedModifier(paused);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.brentvatne.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class AudioBecomingNoisyReceiver extends BroadcastReceiver {
|
||||
|
||||
private final Context context;
|
||||
private BecomingNoisyListener listener = BecomingNoisyListener.NO_OP;
|
||||
|
||||
public AudioBecomingNoisyReceiver(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
|
||||
listener.onAudioBecomingNoisy();
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(BecomingNoisyListener listener) {
|
||||
this.listener = listener;
|
||||
IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
||||
ContextCompat.registerReceiver(context, this, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED);
|
||||
}
|
||||
|
||||
public void removeListener() {
|
||||
this.listener = BecomingNoisyListener.NO_OP;
|
||||
try {
|
||||
context.unregisterReceiver(this);
|
||||
} catch (Exception ignore) {
|
||||
// ignore if already unregistered
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.brentvatne.receiver;
|
||||
|
||||
public interface BecomingNoisyListener {
|
||||
|
||||
BecomingNoisyListener NO_OP = new BecomingNoisyListener() {
|
||||
@Override public void onAudioBecomingNoisy() {
|
||||
// NO_OP
|
||||
}
|
||||
};
|
||||
|
||||
void onAudioBecomingNoisy();
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.google.ads.interactivemedia.v3.api;
|
||||
|
||||
import androidx.annotation.InspectableProperty;
|
||||
|
||||
public abstract class AdError {
|
||||
public abstract InspectableProperty getErrorCode();
|
||||
public abstract InspectableProperty getErrorCodeNumber();
|
||||
public abstract InspectableProperty getErrorType();
|
||||
public abstract String getMessage();
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.google.ads.interactivemedia.v3.api;
|
||||
|
||||
public abstract class AdErrorEvent {
|
||||
public abstract AdError getError();
|
||||
|
||||
public interface AdErrorListener {
|
||||
public void onAdError(AdErrorEvent adErrorEvent);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.google.ads.interactivemedia.v3.api;
|
||||
|
||||
import androidx.annotation.InspectableProperty;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AdEvent {
|
||||
public abstract InspectableProperty getType();
|
||||
public abstract Map<String, String> getAdData();
|
||||
|
||||
public interface AdEventListener {
|
||||
public void onAdEvent(AdEvent adEvent);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layoutDirection="ltr"
|
||||
android:background="#CC000000"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton android:id="@+id/exo_prev"
|
||||
style="@style/ExoMediaButton.Previous"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_rew"
|
||||
style="@style/ExoMediaButton.Rewind"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/exo_play_pause_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
<ImageButton android:id="@+id/exo_play"
|
||||
style="@style/ExoMediaButton.Play"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_pause"
|
||||
style="@style/ExoMediaButton.Pause"/>
|
||||
</FrameLayout>
|
||||
|
||||
<ImageButton android:id="@+id/exo_ffwd"
|
||||
style="@style/ExoMediaButton.FastForward"/>
|
||||
|
||||
<ImageButton android:id="@+id/exo_next"
|
||||
style="@style/ExoMediaButton.Next"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView android:id="@+id/exo_position"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="#FFBEBEBE"/>
|
||||
|
||||
<androidx.media3.ui.DefaultTimeBar
|
||||
android:id="@+id/exo_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="26dp"/>
|
||||
|
||||
<TextView android:id="@+id/exo_duration"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="#FFBEBEBE"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/exo_fullscreen"
|
||||
style="@style/ExoMediaButton.FullScreen"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_margin="4dp"
|
||||
android:scaleType="fitCenter" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
|
||||
|
||||
<string name="error_no_secure_decoder">This device does not provide a secure decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
|
||||
|
||||
<string name="error_querying_decoders">Unable to query device decoders</string>
|
||||
|
||||
<string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string>
|
||||
|
||||
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
|
||||
|
||||
<string name="unrecognized_media_format">Unrecognized media format</string>
|
||||
|
||||
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
|
||||
|
||||
<string name="error_drm_unknown">An unknown DRM error occurred</string>
|
||||
</resources>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="ExoMediaButton.FullScreen" tools:ignore="PrivateResource">
|
||||
<item name="android:src">@drawable/exo_icon_fullscreen_enter</item>
|
||||
<item name="android:contentDescription">@string/exo_controls_fullscreen_enter_description</item>
|
||||
</style>
|
||||
</resources>
|
||||
5
biome.json
Normal file
5
biome.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"formatter": {
|
||||
"useEditorconfig": true
|
||||
}
|
||||
}
|
||||
16
config/.editorconfig
Normal file
16
config/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
quote_type = single
|
||||
44
config/.eslintrc.js
Normal file
44
config/.eslintrc.js
Normal file
@@ -0,0 +1,44 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@react-native', 'plugin:prettier/recommended'],
|
||||
ignorePatterns: [
|
||||
'**/node_modules',
|
||||
'**/lib',
|
||||
'**/.eslintrc.js',
|
||||
'**/.prettierrc.js',
|
||||
'**/jest.config.js',
|
||||
'**/babel.config.js',
|
||||
'**/metro.config.js',
|
||||
'**/react-native.config.js',
|
||||
'**/tsconfig.json',
|
||||
],
|
||||
plugins: ['@typescript-eslint', 'prettier'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
quoteProps: 'consistent',
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'es5',
|
||||
useTabs: false,
|
||||
},
|
||||
],
|
||||
quotes: [
|
||||
'error',
|
||||
'single',
|
||||
{ avoidEscape: true, allowTemplateLiterals: true },
|
||||
],
|
||||
'@react-native/no-deep-imports': 'off',
|
||||
},
|
||||
};
|
||||
36
config/tsconfig.json
Normal file
36
config/tsconfig.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"exclude": [
|
||||
"**/node_modules",
|
||||
"**/lib",
|
||||
"**/.eslintrc.js",
|
||||
"**/.prettierrc.js",
|
||||
"**/jest.config.js",
|
||||
"**/babel.config.js",
|
||||
"**/metro.config.js",
|
||||
"**/react-native.config.js",
|
||||
"**/tsconfig.json"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"lib": ["ESNext", "dom"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmit": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitUseStrict": false,
|
||||
"noStrictGenericChecks": false,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"verbatimModuleSyntax": true
|
||||
}
|
||||
}
|
||||
26
docs/.gitignore
vendored
26
docs/.gitignore
vendored
@@ -1,3 +1,23 @@
|
||||
node_modules/
|
||||
out/
|
||||
.next/
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# API Reference generated files
|
||||
docs/api-reference/
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
# react-native-video-docs
|
||||
### Build
|
||||
|
||||
This is the documentation for the [react-native-video](github.com/react-native-video/react-native-video).
|
||||
Project is using [bun](https://bun.sh) to build and run the documentation.
|
||||
Framework for static site generation is [Nextra](https://nextra.site/docs)
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
$ bun run build
|
||||
```
|
||||
|
||||
To run:
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
### Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
$ USE_SSH=true bun run deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```
|
||||
$ GIT_USER=<Your GitHub username> bun run deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
2811
docs/bun.lock
Normal file
2811
docs/bun.lock
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/bun.lockb
BIN
docs/bun.lockb
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
.paragraphStyle {
|
||||
margin-top: 10;
|
||||
}
|
||||
|
||||
.spanStyle {
|
||||
font-family: 'Orbitron';
|
||||
font-weight: 800;
|
||||
}
|
||||
8
docs/docs/configuration/_category_.json
Normal file
8
docs/docs/configuration/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Configuration",
|
||||
"position": 6,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "React Native Video Configuration."
|
||||
}
|
||||
}
|
||||
85
docs/docs/configuration/expo-plugin.md
Normal file
85
docs/docs/configuration/expo-plugin.md
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_label: Expo Plugin
|
||||
description: Expo plugin for react-native-video configuration
|
||||
---
|
||||
|
||||
# Expo Plugin
|
||||
|
||||
The `react-native-video` library provides an Expo plugin to simplify the integration and configuration of specific features into your Expo project.
|
||||
|
||||
## Installation
|
||||
|
||||
To use the Expo plugin, you need to add it to your app's configuration file (`app.json` or `app.config.js`).
|
||||
|
||||
```json title="app.json"
|
||||
{
|
||||
"expo": {
|
||||
"plugins": [
|
||||
[
|
||||
"react-native-video",
|
||||
{
|
||||
"enableAndroidPictureInPicture": true,
|
||||
"enableBackgroundAudio": true,
|
||||
"androidExtensions": {
|
||||
"useExoplayerDash": true,
|
||||
"useExoplayerHls": true
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```javascript title="app.config.js"
|
||||
export default {
|
||||
plugins: [
|
||||
[
|
||||
'react-native-video',
|
||||
{
|
||||
enableAndroidPictureInPicture: true,
|
||||
enableBackgroundAudio: true,
|
||||
androidExtensions: {
|
||||
useExoplayerDash: true,
|
||||
useExoplayerHls: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
The plugin accepts an optional configuration object with the following properties:
|
||||
|
||||
### `enableAndroidPictureInPicture` (optional)
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `false`
|
||||
- **Description:** Enables Picture-in-Picture (PiP) mode on Android. This will apply the necessary configurations to your Android project.
|
||||
|
||||
### `enableBackgroundAudio` (optional)
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `false`
|
||||
- **Description:** Enables audio playback to continue when the app is in the background on Android. Ensure you have also configured the necessary background modes capabilities in your app if required by the operating system.
|
||||
|
||||
### `androidExtensions` (optional)
|
||||
|
||||
- **Type:** `object`
|
||||
- **Default:** `{ useExoplayerDash: true, useExoplayerHls: true }`
|
||||
- **Description:** Allows you to specify which Android ExoPlayer extensions to include. This can help reduce the size of your app by only including the extensions you need.
|
||||
- `useExoplayerDash` (boolean, default: `true`): Whether to include ExoPlayer's Dash extension.
|
||||
- `useExoplayerHls` (boolean, default: `true`): Whether to include ExoPlayer's HLS extension.
|
||||
|
||||
### `reactNativeTestApp` (optional)
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `false`
|
||||
- **Description:** Whether to use `react-native-test-app` compatible mode.
|
||||
|
||||
## Usage
|
||||
|
||||
Once configured in your `app.json` or `app.config.js`, the plugin will automatically apply the necessary native project changes during the prebuild process (e.g., when running `npx expo prebuild`). No further manual setup is typically required for these features.
|
||||
64
docs/docs/configuration/manual.md
Normal file
64
docs/docs/configuration/manual.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
sidebar_label: Manual Configuration
|
||||
description: Manual configuration of react-native-video
|
||||
---
|
||||
|
||||
# Manual Configuration
|
||||
|
||||
If you prefer not to use the Expo plugin you can configure **react-native-video** manually by editing the native project files directly. The steps below show the exact changes performed by the plugin so you can reproduce them in a plain React Native or bare Expo project.
|
||||
|
||||
---
|
||||
|
||||
## iOS
|
||||
|
||||
### Enable Background Audio
|
||||
To allow video sound to continue when the app goes to the background add the `audio` mode to `Info.plist`:
|
||||
|
||||
```xml title="ios/YourApp/Info.plist"
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
## Android
|
||||
|
||||
### Configure ExoPlayer extensions
|
||||
By default the library enables DASH & HLS extensions. You can fine-tune this by adding properties to **gradle.properties**:
|
||||
|
||||
```properties title="android/gradle.properties"
|
||||
# Enable / disable ExoPlayer extensions used by react-native-video
|
||||
RNVideo_useExoplayerDash=true # DASH playback support
|
||||
RNVideo_useExoplayerHls=true # HLS playback support
|
||||
```
|
||||
Set a value to `false` to exclude the corresponding extension and reduce APK size.
|
||||
|
||||
### Enable Picture-in-Picture (PiP)
|
||||
Add the `android:supportsPictureInPicture` flag to your *main* activity in **AndroidManifest.xml**:
|
||||
|
||||
```xml title="android/app/src/main/AndroidManifest.xml"
|
||||
<application>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:supportsPictureInPicture="true"
|
||||
...>
|
||||
<!-- other attributes -->
|
||||
</activity>
|
||||
</application>
|
||||
```
|
||||
|
||||
PiP requires **API 26+** (Android 8.0). Make sure `minSdkVersion` is at least `26` when enabling this feature.
|
||||
|
||||
## Verification
|
||||
After the modifications:
|
||||
|
||||
1. **iOS** – run `cd ios && pod install` then build the app from Xcode or via `npx react-native run-ios` / `npx expo run:ios`.
|
||||
2. **Android** – clean & rebuild the project: `./gradlew clean && ./gradlew :app:assembleDebug` or simply run `npx react-native run-android` / `npx expo run:android`.
|
||||
|
||||
If the build succeeds your manual configuration is complete.
|
||||
|
||||
---
|
||||
|
||||
### Need an easier way?
|
||||
Use the [Expo plugin](./expo-plugin.md) to apply exactly the same changes automatically during `expo prebuild`.
|
||||
108
docs/docs/events/events.md
Normal file
108
docs/docs/events/events.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
sidebar_label: Events
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Handling Player Events
|
||||
|
||||
The `VideoPlayer` emits a variety of events that allow you to monitor and react to changes in its state and playback.
|
||||
|
||||
## Using the `useEvent` Hook
|
||||
|
||||
For React functional components, the `useEvent` hook provides a convenient way to subscribe to player events and automatically manage cleanup.
|
||||
|
||||
```typescript
|
||||
import { useVideoPlayer, useEvent } from 'react-native-video';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const MyVideoComponent = () => {
|
||||
const player = useVideoPlayer('https://example.com/video.mp4', (_player) => {
|
||||
_player.play();
|
||||
});
|
||||
|
||||
useEvent(player, 'onLoad', (data) => {
|
||||
console.log('Video loaded via useEvent! Duration:', data.duration);
|
||||
});
|
||||
|
||||
useEvent(player, 'onProgress', (data) => {
|
||||
console.log('Progress via useEvent:', data.currentTime);
|
||||
});
|
||||
|
||||
// For onError, which is a direct property on VideoPlayer, not from VideoPlayerEvents
|
||||
useEvent(player, 'onError', (error) => {
|
||||
console.error('Player Error via useEvent:', error.code, error.message);
|
||||
});
|
||||
|
||||
return <VideoView player={player} />;
|
||||
};
|
||||
```
|
||||
|
||||
## Available Events
|
||||
|
||||
The `VideoPlayer` class, through `VideoPlayerEvents`, supports the following events. You can subscribe to these by assigning a callback function to the corresponding property on the `VideoPlayer` instance.
|
||||
|
||||
| Event | Callback Signature | Description |
|
||||
|----------------------------|--------------------------------------------------------|---------------------------------------------------------------------------------------------|
|
||||
| `onAudioBecomingNoisy` | () => void | Fired when audio is about to become noisy (e.g., headphones unplugged). |
|
||||
| `onAudioFocusChange` | (hasAudioFocus: boolean) => void | Fired when the audio focus changes (e.g., another app starts playing audio). |
|
||||
| `onBandwidthUpdate` | (data: [BandwidthData](../api-reference/interfaces/BandwidthData.md)) => void | Fired with an estimate of the available bandwidth. |
|
||||
| `onBuffer` | (buffering: boolean) => void | Fired when the player starts or stops buffering data. |
|
||||
| `onControlsVisibleChange` | (visible: boolean) => void | Fired when the visibility of native controls changes. |
|
||||
| `onEnd` | () => void | Fired when the video playback reaches the end. |
|
||||
| `onExternalPlaybackChange` | (externalPlaybackActive: boolean) => void | Fired when the external playback status changes (e.g., AirPlay). |
|
||||
| `onLoad` | (data: [onLoadData](../api-reference/interfaces/onLoadData.md)) => void | Fired when the video has loaded and is ready to play. |
|
||||
| `onLoadStart` | (data: [onLoadStartData](../api-reference/interfaces/onLoadStartData.md)) => void | Fired when the video starts loading. |
|
||||
| `onPlaybackRateChange` | (rate: number) => void | Fired when the playback rate changes. |
|
||||
| `onPlaybackStateChange` | (data: [onPlaybackStateChangeData](../api-reference/interfaces/onPlaybackStateChangeData.md)) => void | Fired when the playback state changes (e.g., playing, paused, stopped). |
|
||||
| `onProgress` | (data: [onProgressData](../api-reference/interfaces/onProgressData.md)) => void | Fired periodically during playback with the current time. |
|
||||
| `onReadyToDisplay` | () => void | Fired when the player is ready to display the first frame of the video. |
|
||||
| `onSeek` | (seekTime: number) => void | Fired when a seek operation has completed. |
|
||||
| `onStatusChange` | (status: [VideoPlayerStatus](../api-reference/type-aliases/VideoPlayerStatus.md)) => void | Fired when the player status changes (detailed status updates). |
|
||||
| `onTextTrackDataChanged` | (texts: string[]) => void | Fired when text track data (e.g., subtitles) changes. |
|
||||
| `onTimedMetadata` | (metadata: [TimedMetadata](../api-reference/interfaces/TimedMetadata.md)) => void | Fired when timed metadata is encountered in the video stream. |
|
||||
| `onTrackChange` | (track: [TextTrack](../api-reference/interfaces/TextTrack.md) \| null) => void | Fired when the selected text track changes. |
|
||||
| `onVolumeChange` | (data: [onVolumeChangeData](../api-reference/interfaces/onVolumeChangeData.md)) => void | Fired when the volume changes. |
|
||||
|
||||
Additionally, the `VideoPlayer` instance itself has an `onError` property:
|
||||
|
||||
- `onError: (error: ` [VideoRuntimeError](../api-reference/classes/VideoRuntimeError.md) `) => void` — Fired when an error occurs. The callback receives the `VideoRuntimeError` object.
|
||||
|
||||
**Benefits of `useEvent`**:
|
||||
|
||||
- **Automatic Cleanup**: The event listener is automatically removed when the component unmounts or when the `player`, `event`, or `callback` dependencies change, preventing memory leaks.
|
||||
- **Type Safety**: Provides better type inference for event callback parameters.
|
||||
|
||||
This hook is recommended for managing event subscriptions in a declarative React style.
|
||||
|
||||
### Initialization Timing and Events
|
||||
|
||||
`onLoadStart` / `onLoad` will fire automatically after construction when `initializeOnCreation` (default `true`) is enabled. If you set `initializeOnCreation: false`, these events will not fire until you call `initialize()` or `preload()`. Attach your event handlers before invoking those methods to avoid missing early events.
|
||||
|
||||
## Subscribing to Events
|
||||
|
||||
You can subscribe to an event by assigning a function to the player instance's corresponding property:
|
||||
|
||||
```typescript
|
||||
import { VideoPlayer } from 'react-native-video';
|
||||
|
||||
const player = new VideoPlayer('https://example.com/video.mp4');
|
||||
|
||||
player.addEventListener('onLoad', (data) => {
|
||||
console.log('Video loaded! Duration:', data.duration);
|
||||
});
|
||||
|
||||
player.addEventListener('onProgress', (data) => {
|
||||
console.log('Current time:', data.currentTime);
|
||||
});
|
||||
|
||||
player.addEventListener('onError', (error) => {
|
||||
console.error('Player Error:', error.code, error.message);
|
||||
});
|
||||
|
||||
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
|
||||
119
docs/docs/installation.md
Normal file
119
docs/docs/installation.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: React Native Video Installation Guide and Requirements
|
||||
---
|
||||
# Installation
|
||||
|
||||
React Native Video is a library that allows you to play various kind of videos in a React Native application. It is built on top of the [`react-native-nitro-modules`](https://nitro.margelo.com/docs/what-is-nitro) type-safe and extremely fast native modules framework. React Native Video supports both New Architecture and Old Architecture.
|
||||
|
||||
## Requirements
|
||||
|
||||
### System Requirements
|
||||
- iOS `15.0` or higher
|
||||
- Android `6.0` or higher
|
||||
|
||||
### Minimal Package Requirements
|
||||
- `react-native` `0.75.0` or higher
|
||||
- `react-native-nitro-modules` `0.27.2` or higher
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
npm install react-native-video@next react-native-nitro-modules
|
||||
```
|
||||
|
||||
2. Configure Library:
|
||||
You can configure the library in two ways:
|
||||
- [Using expo plugins](./configuration/expo-plugin.md)
|
||||
- [Manually editing needed files](./configuration/manual.md)
|
||||
|
||||
3. Run the project:
|
||||
If you are using Expo, you will need to generate native files:
|
||||
```bash
|
||||
npx expo prebuild
|
||||
```
|
||||
|
||||
And then run the project:
|
||||
```bash
|
||||
npx expo run:ios # run on iOS
|
||||
npx expo run:android # run on Android
|
||||
```
|
||||
|
||||
If you are using React Native CLI, you will need to install Pods for iOS:
|
||||
```bash
|
||||
cd ios && pod install && cd ..
|
||||
```
|
||||
|
||||
And then run the project:
|
||||
```bash
|
||||
npx react-native run-ios # run on iOS
|
||||
npx react-native run-android # run on Android
|
||||
```
|
||||
|
||||
## Patch for react-native < 0.80
|
||||
|
||||
`react-native` < 0.80 have bug that prevents to properly handle errors by nitro modules on Android.
|
||||
We highly recommend to apply bellow patch for `react-native-nitro-modules` to fix this issue.
|
||||
You can apply it using `patch-package`.
|
||||
|
||||
:::warning
|
||||
Without this patch you won't be able "recognize" errors, all will be thrown as unknown errors.
|
||||
:::
|
||||
|
||||
<details>
|
||||
<summary>For `react-native-nitro-modules` 0.27.X or higher</summary>
|
||||
|
||||
```diff
|
||||
diff --git a/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp b/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp
|
||||
index efcea05..ffad3f2 100644
|
||||
--- a/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp
|
||||
+++ b/node_modules/react-native-nitro-modules/cpp/core/HybridFunction.hpp
|
||||
@@ -23,6 +23,10 @@ struct JSIConverter;
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
+#ifdef ANDROID
|
||||
+#include <fbjni/fbjni.h>
|
||||
+#endif
|
||||
+
|
||||
namespace margelo::nitro {
|
||||
|
||||
using namespace facebook;
|
||||
@@ -109,6 +113,15 @@ public:
|
||||
std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
|
||||
std::string message = exception.what();
|
||||
throw jsi::JSError(runtime, funcName + ": " + message);
|
||||
+#ifdef ANDROID
|
||||
+#pragma clang diagnostic push
|
||||
+#pragma clang diagnostic ignored "-Wexceptions"
|
||||
+ } catch (const jni::JniException& exception) {
|
||||
+ std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
|
||||
+ std::string message = exception.what();
|
||||
+ throw jsi::JSError(runtime, funcName + ": " + message);
|
||||
+#pragma clang diagnostic pop
|
||||
+#endif
|
||||
} catch (...) {
|
||||
// Some unknown exception was thrown - add method name information and re-throw as `JSError`.
|
||||
std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
|
||||
```
|
||||
|
||||
see [raw](https://github.com/TheWidlarzGroup/react-native-video/blob/v7/example/patches/react-native-nitro-modules%2B0.27.2.patch)
|
||||
</details>
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx title="App.tsx"
|
||||
import { VideoView, useVideoPlayer } from 'react-native-video';
|
||||
|
||||
export default function App() {
|
||||
const player = useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
},
|
||||
});
|
||||
|
||||
return <VideoView player={player} />;
|
||||
}
|
||||
```
|
||||
53
docs/docs/intro.md
Normal file
53
docs/docs/intro.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: Introduction to React Native Video library
|
||||
---
|
||||
|
||||
# Intro
|
||||
|
||||
**One of the most popular video playback libraries for React Native applications.**
|
||||
|
||||
`react-native-video` provides a comprehensive solution for video playback in React Native, built on top of [`react-native-nitro-modules`](https://nitro.margelo.com/docs/what-is-nitro) framework. Whether you're building a video streaming app, media player, or educational platform, `react-native-video` gives you the tools you need to create exceptional video experiences.
|
||||
|
||||
## Why Choose `react-native-video`?
|
||||
|
||||
### Native Performance
|
||||
`react-native-video` is built with native video players (AVPlayer on iOS, ExoPlayer on Android) to ensure hardware-accelerated playback for smooth performance. The library includes optimized memory management and resource handling to provide the best possible user experience.
|
||||
|
||||
### Rich Feature Set
|
||||
The library offers advanced playback controls including play, pause, seek, volume control, and playback rate adjustment. It supports multiple source types including HTTP/HTTPS streams, local files, HLS, and DASH. `react-native-video` includes subtitle support for both built-in and external subtitle files (WebVTT, SRT), native Picture-in-Picture support on both platforms, background audio playback and native fullscreen implementation.
|
||||
|
||||
### Developer Experience
|
||||
`react-native-video` is TypeScript-first with full TypeScript support and comprehensive type definitions. It provides intuitive React hooks for easy integration. The library is compatible with React Native's New Architecture and works seamlessly with Expo managed and bare workflows.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Get started in minutes with a simple video player:
|
||||
|
||||
```bash
|
||||
npm install react-native-video@next react-native-nitro-modules
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { VideoView, useVideoPlayer } from 'react-native-video';
|
||||
|
||||
export default function App() {
|
||||
const player = useVideoPlayer({
|
||||
uri: 'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
});
|
||||
|
||||
return <VideoView player={player} />;
|
||||
}
|
||||
```
|
||||
|
||||
## What's Next?
|
||||
|
||||
- **[Installation Guide](./installation.md)** - Get started with `react-native-video`
|
||||
- **[VideoPlayer](./player/player.md)** - Learn about the core player functionality
|
||||
- **[VideoView Component](./video-view.md)** - Understand the video display component
|
||||
- **[Event Handling](./events/events.md)** - Master player events and callbacks
|
||||
- **[Configuration](./configuration/expo-plugin.md)** - Configure the player
|
||||
- **[API Reference](./api-reference/index.md)** - Complete API documentation
|
||||
|
||||
[](https://www.thewidlarzgroup.com/react-native-video/?utm_source=rnv&utm_medium=docs&utm_campaign=intro&utm_id=rnv-banner)
|
||||
|
||||
199
docs/docs/player/drm.md
Normal file
199
docs/docs/player/drm.md
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
sidebar_label: DRM
|
||||
---
|
||||
|
||||
# DRM
|
||||
|
||||
## What is DRM (Digital Rights Management)?
|
||||
DRM is a set of access control technologies that are used to protect copyrighted content from unauthorized use and distribution. It allows content owners to control how their digital media is used and distributed.
|
||||
|
||||
### When do you need it?
|
||||
If you are working with copyrighted content and want to prevent unauthorized access or distribution, you will need DRM. It is especially important for streaming services, e-learning platforms, and any application that delivers premium content that you want to protect from piracy.
|
||||
|
||||
### What next?
|
||||
This page explains how to play DRM‑protected content with React Native Video using the official DRM plugin. It covers installing and enabling the plugin, configuring sources with DRM, and platform‑specific notes for Android (Widevine) and iOS/visionOS (FairPlay).
|
||||
|
||||
## Install and enable the DRM plugin
|
||||
|
||||
:::tip Pluginable Architecture
|
||||
React Native Video uses a plugin architecture. DRM support is provided by the `@twg/react-native-video-drm` plugin and is not built into the core package.
|
||||
:::
|
||||
|
||||
1) Install dependencies in your app:
|
||||
|
||||
```sh
|
||||
npm install @twg/react-native-video-drm
|
||||
```
|
||||
|
||||
2) Enable the plugin at app startup (before creating any players):
|
||||
|
||||
```ts
|
||||
// App.tsx (or any place you want to initialize the plugin)
|
||||
import { enable } from '@twg/react-native-video-drm';
|
||||
|
||||
enable();
|
||||
```
|
||||
|
||||
The plugin autolinks on both Android and iOS. Nitro Modules are required because the plugin uses Nitro under the hood.
|
||||
|
||||
## Quick start
|
||||
|
||||
You pass DRM configuration via `VideoConfig.drm` when creating a player or using the `useVideoPlayer` hook. If `drm.type` is omitted, the default is inferred per platform (`widevine` on Android, `fairplay` on iOS/visionOS).
|
||||
|
||||
```tsx
|
||||
import { VideoView, useVideoPlayer } from 'react-native-video';
|
||||
|
||||
export function Player() {
|
||||
const player = useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://example.com/manifest.mpd', // or HLS .m3u8 for FairPlay
|
||||
// On iOS these headers are also used for the default license request
|
||||
headers: { Authorization: 'Bearer <token>' },
|
||||
drm: {
|
||||
// type: 'widevine' | 'fairplay' // optional; inferred by platform
|
||||
licenseUrl: 'https://license.example.com/widevine',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return <VideoView player={player} />;
|
||||
}
|
||||
```
|
||||
|
||||
:::warning
|
||||
You shouldn't include your authorization token directly in the code. Instead, use a backend method to retrieve it at runtime.
|
||||
:::
|
||||
|
||||
## DRM config reference
|
||||
|
||||
All properties are optional unless marked otherwise for a platform. The table below describes each property, the expected type, platforms where it applies, whether it's required, and important notes.
|
||||
|
||||
| Property | Type | Platform | Required | Notes |
|
||||
|---|---:|---|:---:|---|
|
||||
| `type` | `'widevine' \| 'fairplay'` | Android, iOS, visionOS | No (defaulted) | Default inferred per platform when `drm` is present and `type` omitted (Android → `widevine`, iOS/visionOS → `fairplay`). |
|
||||
| `licenseUrl` | `string` | Android, iOS, visionOS | Android: Yes; iOS/visionOS: Yes for default/custom flows | URL of the license (CKC) service. Required for license acquisition. |
|
||||
| `licenseHeaders` | ``Record<string, string>`` | Android | No | Extra headers sent with the Widevine license request. (On iOS, use `source.headers` for license requests.) |
|
||||
| `multiSession` | `boolean` | Android | No | Whether to allow multiple Widevine sessions. |
|
||||
| `certificateUrl` | `string` | iOS, visionOS | Yes (for FairPlay) | URL to fetch the FairPlay application certificate (used to create the SPC). |
|
||||
| `contentId` | `string` | iOS, visionOS | No | If omitted, derived from the `skd://` key URL. Used when creating the SPC. |
|
||||
| `getLicense` | ``(payload) => Promise<string>`` | iOS, visionOS | No | Optional hook for custom FairPlay license logic; must resolve to a base64‑encoded CKC string. |
|
||||
|
||||
Payload shape passed to `getLicense` (iOS/visionOS):
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---:|---|
|
||||
| `contentId` | `string` | Content identifier for the asset. If not provided the player will try to derive it from the `skd://` key URL. |
|
||||
| `licenseUrl` | `string` | The license server URL that should be used for license acquisition. |
|
||||
| `keyUrl` | `string` | The key URL/identifier received from the stream (typically an `skd://` URL). |
|
||||
| `spc` | `string` | The SPC (secure playback context) as a base64‑encoded string. Send raw SPC bytes to your license server (server side may expect raw bytes rather than base64). |
|
||||
|
||||
## Android: Widevine
|
||||
|
||||
- Set `drm.type` to `'widevine'` or omit it (the library will default to Widevine on Android if `drm` is present).
|
||||
- `licenseUrl` is required; `licenseHeaders` and `multiSession` are optional.
|
||||
- Implementation details:
|
||||
- The plugin uses ExoPlayer’s `DefaultDrmSessionManager` and `HttpMediaDrmCallback` with your `licenseUrl` and `licenseHeaders`.
|
||||
- If a first attempt fails due to device security level issues, the plugin retries with `L3` security level.
|
||||
|
||||
Example:
|
||||
|
||||
```tsx
|
||||
useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://example.com/manifest.mpd',
|
||||
drm: {
|
||||
// type: 'widevine', // optional
|
||||
licenseUrl: 'https://license.example.com/widevine',
|
||||
licenseHeaders: { 'X-Custom-Header': 'value' },
|
||||
multiSession: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## iOS and visionOS: FairPlay
|
||||
|
||||
Two ways to get the CKC (license):
|
||||
|
||||
1) Default flow (no `getLicense`):
|
||||
- Required: `certificateUrl`, `licenseUrl`.
|
||||
- The plugin requests the application certificate, generates the SPC, then POSTs the SPC to `licenseUrl`.
|
||||
- It uses `source.headers` (not `drm.licenseHeaders`) for the license request.
|
||||
|
||||
2) Custom flow (provide `getLicense`):
|
||||
- Required: `certificateUrl`, `licenseUrl`, and a `getLicense` implementation.
|
||||
- You receive `{ contentId, licenseUrl, keyUrl, spc }` and must return a base64‑encoded CKC string.
|
||||
|
||||
Notes:
|
||||
- DRM isn’t supported in the iOS Simulator; the plugin returns `null` for DRM in Simulator builds.
|
||||
- If `contentId` isn’t provided, it is derived from the `skd://` key URL.
|
||||
|
||||
Default flow example:
|
||||
|
||||
```tsx
|
||||
useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://example.com/fairplay.m3u8',
|
||||
headers: { Authorization: 'Bearer <token>' }, // used for the license request
|
||||
drm: {
|
||||
// type: 'fairplay', // optional
|
||||
certificateUrl: 'https://license.example.com/fps-cert',
|
||||
licenseUrl: 'https://license.example.com/fps',
|
||||
// contentId: 'my-content-id', // optional
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Custom `getLicense` example:
|
||||
|
||||
:::tip
|
||||
This is example code for a custom `getLicense` implementation. it may differ from your actual implementation provided by your DRM provider
|
||||
:::
|
||||
|
||||
```tsx
|
||||
useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://example.com/fairplay.m3u8',
|
||||
drm: {
|
||||
certificateUrl: 'https://license.example.com/fps-cert',
|
||||
licenseUrl: 'https://license.example.com/fps',
|
||||
getLicense: async ({ contentId, licenseUrl, keyUrl, spc }) => {
|
||||
// Example: POST SPC to your license server and return base64 CKC
|
||||
const res = await fetch(licenseUrl, {
|
||||
method: 'POST',
|
||||
body: Buffer.from(spc, 'base64'), // server expects raw SPC bytes
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Content-ID': contentId,
|
||||
'X-Asset-Id': keyUrl,
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error(`License request failed: ${res.status}`);
|
||||
const ckc = await res.arrayBuffer();
|
||||
// return base64 CKC string
|
||||
return Buffer.from(ckc).toString('base64');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Offline
|
||||
If you are looking for implementing offline playback with DRM, make sure to checkout our [Offline Video SDK](https://www.thewidlarzgroup.com/offline-video-sdk). It provides a comprehensive solution for downloading and playing Streams and DRM-protected content.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- DRMPluginNotFound: Ensure you installed `@twg/react-native-video-drm`, imported it, and called `enable()` before creating any players.
|
||||
- iOS headers: The default FairPlay flow uses `source.headers` for license requests; `drm.licenseHeaders` are not used on iOS.
|
||||
- Invalid CKC: `getLicense` must return a base64 string. Returning raw bytes or JSON will fail.
|
||||
- 403/415 from license server: Verify required auth headers, content type, and whether the server expects raw SPC bytes vs base64.
|
||||
- Android security level issues: The plugin retries with Widevine L3 if the first attempt fails.
|
||||
- iOS Simulator: DRM isn’t supported in Simulator. Test on a real device.
|
||||
|
||||
## Notes and defaults
|
||||
|
||||
- If `drm` is provided without `type`, the library sets a platform default: Android → Widevine, iOS/visionOS → FairPlay.
|
||||
- For custom DRM systems or advanced pipelines, you can implement your own plugin. See the Plugin Interface docs.
|
||||
|
||||
133
docs/docs/player/player-lifecycle.md
Normal file
133
docs/docs/player/player-lifecycle.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
sidebar_label: Player Lifecycle
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Player Lifecycle
|
||||
|
||||
Understanding the lifecycle of the `VideoPlayer` is crucial for managing resources effectively and ensuring a smooth user experience.
|
||||
|
||||
## Creation and Initialization
|
||||
|
||||
1. **Instantiation**: A `VideoPlayer` instance is created by calling its constructor with a video source (URL, `VideoSource`, or `VideoConfig`).
|
||||
```typescript
|
||||
const player = new VideoPlayer('https://example.com/video.mp4');
|
||||
```
|
||||
2. **Native Player Allocation**: A lightweight native player object is allocated immediately.
|
||||
3. **Asset Initialization**: By default (unless you opt out) the underlying media item is prepared **asynchronously right after creation**. You can control this with `initializeOnCreation` inside `VideoConfig`.
|
||||
|
||||
### Deferred Initialization (Advanced)
|
||||
|
||||
If you pass a `VideoConfig` with `{ initializeOnCreation: false }`, the player will skip preparing the media item automatically. This is useful when:
|
||||
|
||||
- You need to batch‑create many players without incurring immediate decoding / network cost
|
||||
- You want to attach event handlers before any network requests happen
|
||||
- You want explicit control over when buffering begins (e.g. on user interaction)
|
||||
|
||||
To initialize later, call:
|
||||
```ts
|
||||
await player.initialize();
|
||||
// or preload if you also want it prepared & ready
|
||||
await player.preload();
|
||||
```
|
||||
|
||||
### Initialization Methods Comparison
|
||||
|
||||
| Method | When to use | What it does |
|
||||
|--------|-------------|--------------|
|
||||
| `initialize()` | You deferred initialization and now want to create the native player item / media source | Creates & attaches the underlying player item / media source without starting playback |
|
||||
| `preload()` | You want the player item prepared (buffering kicked off) ahead of an upcoming `play()` call | Ensures the media source is set and prepared; resolves once preparation started (may already be initialized) |
|
||||
| Implicit (default) | `initializeOnCreation` not set or `true` | Automatically schedules initialization after JS construction |
|
||||
|
||||
:::info
|
||||
By default, the player initializes automatically after construction. If you need to defer initialization, set `initializeOnCreation: false` in the config. You can then call `player.initialize()` or `player.preload()` later to start the player.
|
||||
:::
|
||||
|
||||
## Playing a Video
|
||||
|
||||
1. **Loading**: When the player (auto) initializes, `preload()` is called, or after `replaceSourceAsync()`, the player starts loading the video metadata and buffering content.
|
||||
- `onLoadStart`: Fired when the video starts loading.
|
||||
- `onLoad`: Fired when the video metadata is loaded and the player is ready to play (duration, dimensions, etc., are available).
|
||||
- `onBuffer`: Fired when buffering starts or ends.
|
||||
2. **Playback**: Once enough data is buffered, playback begins.
|
||||
- `onPlaybackStateChange`: Fired when the playback state changes (e.g., from `buffering` to `playing`).
|
||||
- `onProgress`: Fired periodically with the current playback time.
|
||||
- `onReadyToDisplay`: Fired when the first frame is ready to be displayed.
|
||||
|
||||
## Controlling Playback
|
||||
|
||||
- `pause()`: Pauses playback. `status` changes to `paused`.
|
||||
- `seekTo(time)`, `seekBy(time)`: Changes the current playback position. `onSeek` is fired when the seek operation completes.
|
||||
- `set volume(value)`, `set muted(value)`, `set loop(value)`, `set rate(value)`: Modify player properties. Corresponding events like `onVolumeChange` or `onPlaybackRateChange` might be fired.
|
||||
|
||||
## Changing Source
|
||||
|
||||
- `replaceSourceAsync(newSource)`: This method allows you to change the video source dynamically.
|
||||
1. The current native player resources associated with the old source are released (similar to `release()` but specifically for the source).
|
||||
2. A new native player instance (or reconfigured existing one) is prepared for the `newSource`.
|
||||
3. The loading lifecycle events (`onLoadStart`, `onLoad`, etc.) will fire for the new source.
|
||||
- `replaceSourceAsync(null)`: This effectively unloads the current video and releases its associated resources without loading a new one. This is useful for freeing up memory if the player is temporarily not needed but might be used again later.
|
||||
|
||||
## Releasing Resources
|
||||
|
||||
There are two main ways to release resources:
|
||||
|
||||
1. **`replaceSourceAsync(null)`**: This is a less destructive way to free resources related *only* to the current video source.
|
||||
- The `VideoPlayer` instance itself remains usable.
|
||||
- You can later call `replaceSourceAsync(newSource)` to load and play a new video.
|
||||
|
||||
2. **`release()`**: This is a destructive operation.
|
||||
|
||||
:::danger
|
||||
After calling `release()`, the player instance becomes unusable. Any subsequent calls to its methods or property access will result in errors.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
It is recommended to use `replaceSourceAsync(null)` when you want to free resources related to the current video source. You should call `release()` only when you are 100% sure that you don't need the player instance anymore. Anyway garbage collector will release the player instance when it is no longer needed.
|
||||
:::
|
||||
|
||||
## Error Handling
|
||||
|
||||
- The `onError` callback, if provided, will be called when a `VideoRuntimeError` occurs. This allows you to handle issues like network errors, invalid source, or platform-specific playback problems.
|
||||
- If `onError` is not provided, errors might be thrown as exceptions.
|
||||
|
||||
## Using with Hooks (`useVideoPlayer`)
|
||||
|
||||
The `useVideoPlayer` hook simplifies managing the `VideoPlayer` lifecycle within React components.
|
||||
|
||||
```typescript
|
||||
import { useVideoPlayer } from 'react-native-video';
|
||||
|
||||
const MyComponent = () => {
|
||||
const player = useVideoPlayer('https://example.com/video.mp4', (playerInstance) => {
|
||||
// Optional setup function: configure the player instance after creation
|
||||
playerInstance.loop = true;
|
||||
});
|
||||
|
||||
// ... use player ...
|
||||
|
||||
return <VideoView player={player} />;
|
||||
};
|
||||
```
|
||||
|
||||
- **Automatic Creation**: `useVideoPlayer` creates a `VideoPlayer` instance when the component mounts or when the source dependency changes.
|
||||
- **Automatic Cleanup**: It automatically cleanup resources when the component unmounts or before recreating the player due to a source change. This prevents resource leaks.
|
||||
- **Dependency Management**: If the `source` prop passed to `useVideoPlayer` changes, the hook will clean up the old player instance and create a new one with the new source.
|
||||
|
||||
:::tip
|
||||
Using `useVideoPlayer` is the recommended way to manage `VideoPlayer` instances in functional components to ensure proper lifecycle management and resource cleanup. It will also respect `initializeOnCreation` (defaults to `true`). If you need deferred initialization with the hook:
|
||||
|
||||
```tsx
|
||||
const player = useVideoPlayer({
|
||||
source: { uri: 'https://example.com/video.mp4' },
|
||||
initializeOnCreation: false,
|
||||
}, (instance) => {
|
||||
// Attach listeners first
|
||||
instance.onLoad = () => console.log('Loaded');
|
||||
});
|
||||
|
||||
// Later (e.g. on user tap)
|
||||
await player.initialize(); // or player.preload()
|
||||
player.play();
|
||||
```
|
||||
:::
|
||||
150
docs/docs/player/player.md
Normal file
150
docs/docs/player/player.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
sidebar_label: Player
|
||||
---
|
||||
|
||||
# Player
|
||||
|
||||
The `VideoPlayer` class is the primary way to control video playback. It provides methods and properties to manage the video source, playback state, volume, and other aspects of the video.
|
||||
|
||||
## Initialization
|
||||
|
||||
To use the `VideoPlayer`, you first need to create an instance of it with a video source. There are two ways to do this. By default the native media item is initialized asynchronously right after creation (unless you opt out with `initializeOnCreation: false`).
|
||||
|
||||
using `useVideoPlayer` hook
|
||||
```tsx
|
||||
import { useVideoPlayer } from 'react-native-video';
|
||||
|
||||
const player = useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
`useVideoPlayer` hook is recommended for most use cases. It automatically manages the player lifecycle between the component mount and unmount.
|
||||
:::
|
||||
|
||||
or using `VideoPlayer` class constructor directly
|
||||
```typescript
|
||||
import { VideoPlayer } from 'react-native-video';
|
||||
|
||||
// Using a URL string
|
||||
const player = new VideoPlayer('https://example.com/video.mp4');
|
||||
|
||||
// Using a VideoSource object
|
||||
const playerWithSource = new VideoPlayer({ uri: 'https://example.com/video.mp4' });
|
||||
|
||||
// Using a VideoConfig object
|
||||
const playerWithConfig = new VideoPlayer({
|
||||
source: { uri: 'https://example.com/video.mp4' },
|
||||
// other configurations
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
When using `VideoPlayer` class directly, you need to manually manage the player lifecycle. Once you no longer need the player, you need to call `release()` method to release the player's native resources. see [Player Lifecycle](./player-lifecycle.md) for more details.
|
||||
:::
|
||||
|
||||
## Core Functionality
|
||||
|
||||
The `VideoPlayer` class offers a comprehensive set of methods and properties to control video playback:
|
||||
|
||||
### Playback Control
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `play()` | Starts or resumes video playback. |
|
||||
| `pause()` | Pauses video playback. |
|
||||
| `seekBy(time: number)` | Seeks the video forward or backward by the specified number of seconds. |
|
||||
| `seekTo(time: number)` | Seeks the video to a specific time in seconds. |
|
||||
| `replaceSourceAsync(source: VideoSource \| VideoConfig \| null)` | Replaces the current video source with a new one. Pass `null` to release the current source without replacing it. |
|
||||
| `initialize()` | Manually initialize the underlying native player item when `initializeOnCreation` was set to `false`. No-op if already initialized. |
|
||||
| `preload()` | Ensures the media source is set and prepared (buffering started) without starting playback. If not yet initialized it will initialize first. |
|
||||
| `release()` | Releases the player's native resources. The player is no longer usable after calling this method. **Note:** If you intend to reuse the player instance with a different source, use `replaceSourceAsync(null)` to clear resources instead of `release()`. |
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Access | Type | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `source` | Read-only | `VideoPlayerSource` | Gets the current `VideoPlayerSource` object. |
|
||||
| `status` | Read-only | `VideoPlayerStatus` | Gets the current status (e.g., `playing`, `paused`, `buffering`). |
|
||||
| `duration` | Read-only | `number` | Gets the total duration of the video in seconds. Returns `NaN` until metadata is loaded. |
|
||||
| `volume` | Read/Write | `number` | Gets or sets the player volume (0.0 to 1.0). |
|
||||
| `currentTime` | Read/Write | `number` | Gets or sets the current playback time in seconds. |
|
||||
| `muted` | Read/Write | `boolean` | Gets or sets whether the video is muted. |
|
||||
| `loop` | Read/Write | `boolean` | Gets or sets whether the video should loop. |
|
||||
| `rate` | Read/Write | `number` | Gets or sets the playback rate (e.g., 1.0 for normal speed, 0.5 for half speed, 2.0 for double speed). |
|
||||
| `mixAudioMode` | Read/Write | `MixAudioMode` | Controls how this player's audio mixes with other audio sources (see [MixAudioMode](../api-reference/type-aliases/MixAudioMode.md)). |
|
||||
| `ignoreSilentSwitchMode` | Read/Write | `IgnoreSilentSwitchMode` | iOS-only. Determines how audio should behave when the hardware mute (silent) switch is on. |
|
||||
| `playInBackground` | Read/Write | `boolean` | Whether playback should continue when the app goes to the background. |
|
||||
| `playWhenInactive` | Read/Write | `boolean` | Whether playback should continue when the app is inactive (e.g., during a phone call). |
|
||||
| `isPlaying` | Read-only | `boolean` | Returns `true` if the video is currently playing. |
|
||||
| `selectedTrack` | Read-only | `TextTrack \| undefined` | Currently selected text track, or `undefined` when no track is selected. |
|
||||
|
||||
### Error Handling
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `onError?` | `(error: VideoRuntimeError) => void` | A callback function that is invoked when a runtime error occurs in the player. You can use this to catch and handle errors gracefully. |
|
||||
|
||||
### Buffer Config
|
||||
|
||||
You can fine‑tune buffering via `bufferConfig` on the `VideoConfig` you pass to `useVideoPlayer`/`VideoPlayer`. This controls how much data is buffered, live latency targets, and iOS network constraints.
|
||||
|
||||
Example
|
||||
|
||||
```ts
|
||||
const player = useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://example.com/stream.m3u8',
|
||||
bufferConfig: {
|
||||
// Android
|
||||
minBufferMs: 5000,
|
||||
maxBufferMs: 10000,
|
||||
// iOS
|
||||
preferredForwardBufferDurationMs: 3000,
|
||||
// Live (cross‑platform target)
|
||||
livePlayback: { targetOffsetMs: 500 },
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Android
|
||||
Properties below are Android‑only
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `minBufferMs` | `number` | Minimum media duration the player attempts to keep buffered (ms). Default: 5000. |
|
||||
| `maxBufferMs` | `number` | Maximum media duration the player attempts to buffer (ms). Default: 10000. |
|
||||
| `bufferForPlaybackMs` | `number` | Media that must be buffered before playback can start or resume after user action (ms). Default: 1000. |
|
||||
| `bufferForPlaybackAfterRebufferMs` | `number` | Media that must be buffered to resume after a rebuffer (ms). Default: 2000. |
|
||||
| `backBufferDurationMs` | `number` | Duration kept behind the current position to allow instant rewind without rebuffer (ms). |
|
||||
| `livePlayback.minPlaybackSpeed` | `number` | Minimum playback speed used to maintain target live offset. |
|
||||
| `livePlayback.maxPlaybackSpeed` | `number` | Maximum playback speed used to catch up to target live offset. |
|
||||
| `livePlayback.minOffsetMs` | `number` | Minimum allowed live offset (ms). |
|
||||
| `livePlayback.maxOffsetMs` | `number` | Maximum allowed live offset (ms). |
|
||||
| `livePlayback.targetOffsetMs` | `number` | Target live offset the player tries to maintain (ms). |
|
||||
|
||||
#### iOS, visionOS, tvOS
|
||||
Properties below are Apple platforms‑only
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `preferredForwardBufferDurationMs` | `number` | Preferred duration the player attempts to retain ahead of the playhead (ms). |
|
||||
| `preferredPeakBitRate` | `number` | Desired limit of network bandwidth for loading the current item (bits per second). |
|
||||
| `preferredMaximumResolution` | `{ width: number; height: number }` | Preferred maximum video resolution. |
|
||||
| `preferredPeakBitRateForExpensiveNetworks` | `number` | Bandwidth limit for expensive networks (e.g., cellular), in bits per second. |
|
||||
| `preferredMaximumResolutionForExpensiveNetworks` | `{ width: number; height: number }` | Preferred maximum resolution on expensive networks. |
|
||||
| `livePlayback.targetOffsetMs` | `number` | Target live offset (ms) the player will try to maintain. |
|
||||
|
||||
## DRM
|
||||
|
||||
Protected content is supported via a plugin. See the full DRM guide: [DRM](./drm.md).
|
||||
|
||||
Quick notes:
|
||||
- Install and enable the official plugin `@twg/react-native-video-drm` and call `enable()` at app startup before creating players.
|
||||
- Pass DRM configuration on the source using the `drm` property of `VideoConfig` (see the DRM guide for platform specifics and `getLicense` examples).
|
||||
- If you defer initialization (`initializeOnCreation: false`), be sure to call `await player.initialize()` (or `preload()`) before expecting DRM license acquisition events.
|
||||
100
docs/docs/plugins/examples.md
Normal file
100
docs/docs/plugins/examples.md
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Plugin Usage Examples
|
||||
description: Simple examples of implementing common plugin scenarios for React Native Video
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Plugin Usage Examples
|
||||
|
||||
This document provides practical examples of implementing common plugin scenarios for React Native Video.
|
||||
|
||||
## Basic Plugin Template
|
||||
|
||||
```kotlin title="Android"
|
||||
class MyPlugin : ReactNativeVideoPlugin("MyPlugin") {
|
||||
override fun onPlayerCreated(player: WeakReference<NativeVideoPlayer>) {
|
||||
Log.d("MyPlugin", "Player created with uri ${player.get()?.source.uri}")
|
||||
}
|
||||
|
||||
override fun onPlayerDestroyed(player: WeakReference<NativeVideoPlayer>) {
|
||||
Log.d("MyPlugin", "Player destroyed")
|
||||
}
|
||||
|
||||
override fun overrideSource(source: NativeVideoPlayerSource): NativeVideoPlayerSource {
|
||||
Log.d("MyPlugin", "Overriding source with uri ${source.uri}")
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
val plugin = MyPlugin() // Automatically registered
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
class MyPlugin: ReactNativeVideoPlugin {
|
||||
init() {
|
||||
super.init(name: "MyPlugin")
|
||||
}
|
||||
|
||||
override func onPlayerCreated(player: Weak<NativeVideoPlayer>) {
|
||||
// Custom logic when player is created
|
||||
}
|
||||
|
||||
override func onPlayerDestroyed(player: Weak<NativeVideoPlayer>) {
|
||||
// Custom cleanup when player is destroyed
|
||||
}
|
||||
|
||||
override func overrideSource(source: NativeVideoPlayerSource) async -> NativeVideoPlayerSource {
|
||||
// Modify source if needed
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
let plugin = MyPlugin() // Automatically registered
|
||||
```
|
||||
|
||||
## DRM Plugin
|
||||
|
||||
Implement custom DRM handling for protected content.
|
||||
|
||||
:::warning
|
||||
|
||||
DRM plugins are not supported yet in React Native Video. `getDRMManager` is not implemented yet and will have no effect.
|
||||
|
||||
:::
|
||||
|
||||
```kotlin title="Android"
|
||||
class CustomDRMPlugin : ReactNativeVideoPlugin("CustomDRM") {
|
||||
override fun getDRMManager(source: NativeVideoPlayerSource): Any? {
|
||||
if (source.isDRMProtected() && source.drmType == "custom") {
|
||||
return CustomDRMManager(
|
||||
licenseUrl = source.drmLicenseUrl,
|
||||
certificateUrl = source.drmCertificateUrl,
|
||||
keyId = source.drmKeyId
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
class CustomDRMPlugin: ReactNativeVideoPlugin {
|
||||
init() {
|
||||
super.init(name: "CustomDRM")
|
||||
}
|
||||
|
||||
override func getDRMManager(source: NativeVideoPlayerSource) async -> Any? {
|
||||
guard source.isDRMProtected() && source.drmType == "custom" else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CustomDRMManager(
|
||||
licenseUrl: source.drmLicenseUrl,
|
||||
certificateUrl: source.drmCertificateUrl,
|
||||
keyId: source.drmKeyId
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
355
docs/docs/plugins/interface.md
Normal file
355
docs/docs/plugins/interface.md
Normal file
@@ -0,0 +1,355 @@
|
||||
---
|
||||
title: Plugin Interface
|
||||
description: Reference for the React Native Video plugin interface
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Plugin Interface Reference
|
||||
|
||||
This document provides a complete reference for the `ReactNativeVideoPluginSpec` interface and the base `ReactNativeVideoPlugin` implementation.
|
||||
|
||||
## ReactNativeVideoPluginSpec Interface
|
||||
|
||||
### Required Properties
|
||||
|
||||
#### id: String
|
||||
Unique identifier for the plugin. Must be unique across all registered plugins.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
override val id: String = "my_unique_plugin_id"
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
var id: String { "my_unique_plugin_id" }
|
||||
```
|
||||
|
||||
#### name: String
|
||||
Human-readable name for the plugin, used in debug logging.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
override val name: String = "My Custom Plugin"
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
var name: String { "My Custom Plugin" }
|
||||
```
|
||||
|
||||
## Lifecycle Methods
|
||||
|
||||
### Player Lifecycle
|
||||
|
||||
#### onPlayerCreated
|
||||
Called when a new player instance is created.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
@UnstableApi
|
||||
fun onPlayerCreated(player: WeakReference<NativeVideoPlayer>)
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
func onPlayerCreated(player: Weak<NativeVideoPlayer>)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `player`: Weak reference to the newly created player instance
|
||||
|
||||
**Use Cases:**
|
||||
- Initialize player-specific resources
|
||||
- Set up player event listeners
|
||||
- Configure player settings
|
||||
|
||||
#### onPlayerDestroyed
|
||||
Called when a player instance is being destroyed.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
@UnstableApi
|
||||
fun onPlayerDestroyed(player: WeakReference<NativeVideoPlayer>)
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
func onPlayerDestroyed(player: Weak<NativeVideoPlayer>)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `player`: Weak reference to the player instance being destroyed
|
||||
|
||||
**Use Cases:**
|
||||
- Clean up player-specific resources
|
||||
- Remove event listeners
|
||||
- Save state or analytics data
|
||||
|
||||
### Video View Lifecycle
|
||||
|
||||
#### onVideoViewCreated
|
||||
Called when a new video view is created.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
@UnstableApi
|
||||
fun onVideoViewCreated(view: WeakReference<VideoView>)
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
func onVideoViewCreated(view: Weak<VideoComponentView>)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `view`: Weak reference to the newly created video view
|
||||
|
||||
**Use Cases:**
|
||||
- Configure view-specific settings
|
||||
- Set up UI event handlers
|
||||
- Initialize view overlays
|
||||
|
||||
#### onVideoViewDestroyed
|
||||
Called when a video view is being destroyed.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
@UnstableApi
|
||||
fun onVideoViewDestroyed(view: WeakReference<VideoView>)
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
func onVideoViewDestroyed(view: Weak<VideoComponentView>)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `view`: Weak reference to the video view being destroyed
|
||||
|
||||
**Use Cases:**
|
||||
- Clean up view-specific resources
|
||||
- Remove UI event handlers
|
||||
- Save view state
|
||||
|
||||
## Content Modification Methods
|
||||
|
||||
### overrideSource
|
||||
Modify the video source before it's processed by the player.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
fun overrideSource(source: NativeVideoPlayerSource): NativeVideoPlayerSource
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
func overrideSource(source: NativeVideoPlayerSource) async -> NativeVideoPlayerSource
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `source`: The original video source
|
||||
|
||||
**Returns:**
|
||||
- Modified video source (can be the same instance if no changes needed)
|
||||
|
||||
**Use Cases:**
|
||||
- Add authentication headers
|
||||
- Modify URLs (e.g., CDN switching)
|
||||
- Add tracking parameters
|
||||
- Transform source format
|
||||
|
||||
### getDRMManager
|
||||
Provide a custom DRM manager for protected content.
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
fun getDRMManager(source: NativeVideoPlayerSource): Any?
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
func getDRMManager(source: NativeVideoPlayerSource) async -> Any?
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `source`: The video source that may require DRM
|
||||
|
||||
**Returns:**
|
||||
- DRM manager instance, or `null` if this plugin doesn't handle DRM for this source
|
||||
|
||||
**Use Cases:**
|
||||
- Widevine DRM implementation
|
||||
- FairPlay DRM implementation
|
||||
- Custom DRM solutions
|
||||
- License acquisition logic
|
||||
|
||||
## Android-Specific Methods
|
||||
|
||||
### getMediaDataSourceFactory
|
||||
Override the data source factory used by ExoPlayer.
|
||||
|
||||
```kotlin
|
||||
fun getMediaDataSourceFactory(
|
||||
source: NativeVideoPlayerSource,
|
||||
mediaDataSourceFactory: DataSource.Factory
|
||||
): DataSource.Factory?
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `source`: The video source
|
||||
- `mediaDataSourceFactory`: The default data source factory
|
||||
|
||||
**Returns:**
|
||||
- Custom data source factory, or `null` to use the default
|
||||
|
||||
**Use Cases:**
|
||||
- Custom caching strategies
|
||||
- Network optimization
|
||||
- Custom authentication
|
||||
- Analytics data collection
|
||||
|
||||
### getMediaSourceFactory
|
||||
Override the media source factory used by ExoPlayer.
|
||||
|
||||
```kotlin
|
||||
fun getMediaSourceFactory(
|
||||
source: NativeVideoPlayerSource,
|
||||
mediaSourceFactory: MediaSource.Factory,
|
||||
mediaDataSourceFactory: DataSource.Factory
|
||||
): MediaSource.Factory?
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `source`: The video source
|
||||
- `mediaSourceFactory`: The default media source factory
|
||||
- `mediaDataSourceFactory`: The data source factory
|
||||
|
||||
**Returns:**
|
||||
- Custom media source factory, or `null` to use the default
|
||||
|
||||
**Use Cases:**
|
||||
- Custom media format support
|
||||
- Advanced ExoPlayer configuration
|
||||
- Source-specific optimizations
|
||||
|
||||
### getMediaItemBuilder
|
||||
Override the media item builder used by ExoPlayer.
|
||||
|
||||
```kotlin
|
||||
fun getMediaItemBuilder(
|
||||
source: NativeVideoPlayerSource,
|
||||
mediaItemBuilder: MediaItem.Builder
|
||||
): MediaItem.Builder?
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `source`: The video source
|
||||
- `mediaItemBuilder`: The default media item builder
|
||||
|
||||
**Returns:**
|
||||
- Modified media item builder, or `null` to use the default
|
||||
|
||||
**Use Cases:**
|
||||
- Add custom metadata
|
||||
- Configure subtitles
|
||||
- Set playback preferences
|
||||
- Configure DRM settings
|
||||
|
||||
### shouldDisableCache
|
||||
Control whether caching should be disabled for a source.
|
||||
|
||||
```kotlin
|
||||
fun shouldDisableCache(source: NativeVideoPlayerSource): Boolean
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `source`: The video source
|
||||
|
||||
**Returns:**
|
||||
- `true` to disable caching, `false` to allow caching
|
||||
|
||||
**Use Cases:**
|
||||
- Disable caching for live streams
|
||||
- Disable caching for DRM content
|
||||
- Custom caching policies
|
||||
|
||||
## Base Implementation: ReactNativeVideoPlugin
|
||||
|
||||
The base class provides default implementations for all methods:
|
||||
|
||||
### Automatic Registration
|
||||
```kotlin
|
||||
// Android
|
||||
init {
|
||||
PluginsRegistry.shared.register(this)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
public init(name: String) {
|
||||
self.name = name
|
||||
self.id = "RNV_Plugin_\(name)"
|
||||
PluginsRegistry.shared.register(plugin: self)
|
||||
}
|
||||
```
|
||||
|
||||
### Automatic Cleanup (iOS only)
|
||||
```swift
|
||||
deinit {
|
||||
PluginsRegistry.shared.unregister(plugin: self)
|
||||
}
|
||||
```
|
||||
|
||||
### Default Implementations
|
||||
|
||||
All methods have sensible defaults:
|
||||
- Lifecycle methods: No-op implementations
|
||||
- `overrideSource`: Returns the original source unchanged
|
||||
- `getDRMManager`: Returns `null`
|
||||
- Factory methods (Android): Return `null` (use defaults)
|
||||
- `shouldDisableCache`: Returns `false`
|
||||
|
||||
## Method Calling Order
|
||||
|
||||
### Source Processing Flow
|
||||
1. `overrideSource` - Called for each registered plugin in order
|
||||
2. `getDRMManager` - Called for each plugin until one returns non-null
|
||||
3. Factory methods (Android) - Called for each plugin until one returns non-null
|
||||
|
||||
### Lifecycle Flow
|
||||
1. View/Player creation methods called for all plugins
|
||||
2. Source processing happens during playback
|
||||
3. View/Player destruction methods called for all plugins
|
||||
|
||||
## Error Handling
|
||||
|
||||
### DRM Plugin Not Found
|
||||
If no plugin provides a DRM manager when required:
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
throw LibraryError.DRMPluginNotFound
|
||||
```
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
throw LibraryError.DRMPluginNotFound.error()
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
- Return `null` from optional methods when not providing custom behavior
|
||||
- Handle weak reference nullability properly
|
||||
- Use appropriate error handling in async methods (iOS)
|
||||
- Log meaningful debug information
|
||||
|
||||
## Platform Differences Summary
|
||||
|
||||
| Feature | Android | iOS |
|
||||
|---------|---------|-----|
|
||||
| Async Support | No | Yes (async/await) |
|
||||
| Media Factories | Full ExoPlayer support | Limited AVFoundation |
|
||||
| Cache Control | Yes | No |
|
||||
| Auto Cleanup | Manual | Automatic (deinit) |
|
||||
| Weak References | `WeakReference<T>` | `Weak<T>` |
|
||||
102
docs/docs/plugins/plugins.mdx
Normal file
102
docs/docs/plugins/plugins.mdx
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
title: Plugins
|
||||
description: Overview of the React Native Video plugin system
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Plugins Overview
|
||||
|
||||
The **React Native Video** library offers a robust plugin system to extend and customize video functionality on Android and iOS. With plugins, you can override source handling, implement custom DRM, modify media factories, and respond to player lifecycle events.
|
||||
|
||||
:::tip Use in Third Party Library Guide
|
||||
If you are looking how to import React Native Video in your native code to use plugins, you can checkout [Use in Third Party Library](../use-in-third-party-library) page.
|
||||
:::
|
||||
|
||||
## What Are Plugins?
|
||||
|
||||
Plugins are modular extensions that hook into the video player's lifecycle and processing pipeline. They allow you to:
|
||||
|
||||
- **Customize video sources** before playback
|
||||
- **Implement custom DRM** for protected content
|
||||
- **Override media factories** (Android only)
|
||||
- **React to player lifecycle events** for analytics, logging, or cleanup
|
||||
- **Control caching behavior** for performance
|
||||
|
||||
## Architecture
|
||||
|
||||
The plugin system consists of three main components:
|
||||
|
||||
| Component | Description |
|
||||
|:-----------------------------|:-------------------------------------------------------------------------------------------|
|
||||
| `PluginsRegistry` | Singleton that manages plugin registration and coordinates plugin interactions |
|
||||
| `ReactNativeVideoPluginSpec` | Interface/protocol defining the contract all plugins must implement |
|
||||
| `ReactNativeVideoPlugin` | Base implementation with convenient defaults; override only the methods you care about |
|
||||
|
||||
:::tip
|
||||
Plugins are automatically registered when you instantiate a class that extends `ReactNativeVideoPlugin`.
|
||||
:::
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Plugin Lifecycle
|
||||
|
||||
Plugins receive notifications for key events:
|
||||
|
||||
- **Player creation/destruction** — react to player instance lifecycle
|
||||
- **Video view creation/destruction** — handle UI component lifecycle
|
||||
- **Source processing** — modify video sources before playback
|
||||
- **DRM requests** — provide custom DRM handling
|
||||
- **Media factory overrides** — customize ExoPlayer components (Android)
|
||||
|
||||
:::caution
|
||||
All player and view references passed to plugins are **weak references** to prevent memory leaks.
|
||||
:::
|
||||
|
||||
## Platform Differences
|
||||
|
||||
| Feature | Android (Kotlin / ExoPlayer) | iOS (Swift / AVFoundation) |
|
||||
|:-------------------------------|:-----------------------------------------------|:-----------------------------------|
|
||||
| Underlying player | ExoPlayer | AVFoundation |
|
||||
| Media factory customization | Extensive | Limited |
|
||||
| Cache control | Supported | Limited |
|
||||
| Plugin method style | Synchronous | Async/await |
|
||||
| Memory management | Manual cleanup | Manual cleanup |
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Example: Android Plugin
|
||||
|
||||
```kotlin
|
||||
// Android
|
||||
class MyPlugin : ReactNativeVideoPlugin("MyPluginName") {
|
||||
override fun onPlayerCreated(player: WeakReference<NativeVideoPlayer>) {
|
||||
// Custom logic here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example: iOS Plugin
|
||||
|
||||
```swift
|
||||
// iOS
|
||||
class MyPlugin: ReactNativeVideoPlugin {
|
||||
init() {
|
||||
super.init(name: "MyPluginName")
|
||||
}
|
||||
|
||||
override func onPlayerCreated(player: Weak<NativeVideoPlayer>) {
|
||||
// Custom logic here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [**Plugin Registry**](./registry) — Learn about plugin management and registration
|
||||
- [**Plugin Interface**](./interface) — Complete API reference for plugin methods
|
||||
- [**Usage Examples**](./examples) — Practical implementations and common patterns
|
||||
|
||||
|
||||
|
||||
188
docs/docs/plugins/registry.md
Normal file
188
docs/docs/plugins/registry.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
title: Plugin Registry
|
||||
description: Overview of the React Native Video plugin registry and how to use it
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Plugin Registry
|
||||
|
||||
The `PluginsRegistry` is a singleton that manages all plugin instances and coordinates their interactions with the video player system. It handles registration, unregistration, and notification distribution to all active plugins.
|
||||
|
||||
## Singleton Pattern
|
||||
|
||||
Both Android and iOS use a shared singleton:
|
||||
|
||||
```kotlin title="Android"
|
||||
// Register
|
||||
PluginsRegistry.shared.register(plugin)
|
||||
// Unregister
|
||||
PluginsRegistry.shared.unregister(plugin)
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
// Register
|
||||
PluginsRegistry.shared.register(plugin: plugin)
|
||||
// Unregister
|
||||
PluginsRegistry.shared.unregister(plugin: plugin)
|
||||
```
|
||||
|
||||
:::tip Plugin Ordering
|
||||
Plugins are processed in registration order. Later plugins can override earlier ones.
|
||||
:::
|
||||
|
||||
## Registration Methods
|
||||
|
||||
### Automatic Registration
|
||||
|
||||
You can use the base class `ReactNativeVideoPlugin` to automatically register your plugin. This will also mock all the methods that are not implemented in your plugin.
|
||||
|
||||
:::danger
|
||||
You still need to unregister your plugin manually when you are done with it. Otherwise, you will have a memory leak.
|
||||
:::
|
||||
|
||||
```kotlin title="Android"
|
||||
class MyPlugin : ReactNativeVideoPlugin("MyPlugin") {
|
||||
// ...
|
||||
}
|
||||
val plugin = MyPlugin() // Auto-registered
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
class MyPlugin: ReactNativeVideoPlugin {
|
||||
init() {
|
||||
super.init(name: "MyPlugin")
|
||||
}
|
||||
// Auto-unregistered in deinit
|
||||
}
|
||||
let plugin = MyPlugin() // Auto-registered
|
||||
```
|
||||
|
||||
### Manual Registration
|
||||
|
||||
You can also manually register your plugin. This is useful if you want to implement a plugin that is not a subclass of `ReactNativeVideoPlugin`.
|
||||
You will need to implement the `ReactNativeVideoPluginSpec` interface. This is a protocol that defines the methods and properties that a plugin must implement.
|
||||
|
||||
```kotlin title="Android"
|
||||
class MyCustomPlugin : ReactNativeVideoPluginSpec {
|
||||
override val id = "my_custom_id"
|
||||
override val name = "MyCustomPlugin"
|
||||
// ...
|
||||
}
|
||||
val plugin = MyCustomPlugin()
|
||||
PluginsRegistry.shared.register(plugin)
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
class MyCustomPlugin: ReactNativeVideoPluginSpec {
|
||||
let id = "my_custom_id"
|
||||
let name = "MyCustomPlugin"
|
||||
// ...
|
||||
}
|
||||
let plugin = MyCustomPlugin()
|
||||
PluginsRegistry.shared.register(plugin: plugin)
|
||||
```
|
||||
|
||||
## Plugin ID Generation
|
||||
|
||||
When using the base class, IDs are auto-generated:
|
||||
|
||||
```kotlin title="Android"
|
||||
ID Format: "RNV_Plugin_{name}"
|
||||
Example: "RNV_Plugin_MyCustomDRM"
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
ID Format: "RNV_Plugin_{name}"
|
||||
Example: "RNV_Plugin_MyCustomDRM"
|
||||
```
|
||||
|
||||
## Plugin Internals
|
||||
|
||||
Bellow are the internals of the plugin registry, that shows logic for certain methods.
|
||||
|
||||
### Source Processing
|
||||
|
||||
The registry coordinates source modifications:
|
||||
|
||||
```kotlin title="Android"
|
||||
internal fun overrideSource(source: NativeVideoPlayerSource): NativeVideoPlayerSource {
|
||||
var overriddenSource = source
|
||||
for (plugin in plugins.values) {
|
||||
overriddenSource = plugin.overrideSource(overriddenSource)
|
||||
}
|
||||
return overriddenSource
|
||||
}
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
internal func overrideSource(source: NativeVideoPlayerSource) async -> NativeVideoPlayerSource {
|
||||
var overriddenSource = source
|
||||
for plugin in plugins.values {
|
||||
overriddenSource = await plugin.overrideSource(source: source)
|
||||
}
|
||||
return overriddenSource
|
||||
}
|
||||
```
|
||||
|
||||
### DRM Manager Resolution
|
||||
|
||||
Finds the first plugin that can provide a DRM manager:
|
||||
|
||||
```kotlin title="Android"
|
||||
internal fun getDRMManager(source: NativeVideoPlayerSource): Any {
|
||||
for (plugin in plugins.values) {
|
||||
val manager = plugin.getDRMManager(source)
|
||||
if (manager != null) return manager
|
||||
}
|
||||
throw LibraryError.DRMPluginNotFound
|
||||
}
|
||||
```
|
||||
|
||||
```swift title="iOS"
|
||||
internal func getDrmManager(source: NativeVideoPlayerSource) async throws -> Any? {
|
||||
for plugin in plugins.values {
|
||||
if let drmManager = await plugin.getDRMManager(source: source) {
|
||||
return drmManager
|
||||
}
|
||||
}
|
||||
throw LibraryError.DRMPluginNotFound.error()
|
||||
}
|
||||
```
|
||||
|
||||
## Android-Specific Registry Methods
|
||||
|
||||
| Method Name | Purpose |
|
||||
|:----------------------------------|:----------------------------------------------------------|
|
||||
| `overrideMediaDataSourceFactory` | Override data source factory for custom ExoPlayer sources |
|
||||
| `overrideMediaSourceFactory` | Override media source factory |
|
||||
| `overrideMediaItemBuilder` | Customize the media item builder |
|
||||
| `shouldDisableCache` | Control caching behavior |
|
||||
|
||||
Example signatures:
|
||||
|
||||
```kotlin
|
||||
internal fun overrideMediaDataSourceFactory(
|
||||
source: NativeVideoPlayerSource,
|
||||
mediaDataSourceFactory: DataSource.Factory
|
||||
): DataSource.Factory
|
||||
|
||||
internal fun overrideMediaSourceFactory(
|
||||
source: NativeVideoPlayerSource,
|
||||
mediaSourceFactory: MediaSource.Factory,
|
||||
mediaDataSourceFactory: DataSource.Factory
|
||||
): MediaSource.Factory
|
||||
|
||||
internal fun overrideMediaItemBuilder(
|
||||
source: NativeVideoPlayerSource,
|
||||
mediaItemBuilder: MediaItem.Builder
|
||||
): MediaItem.Builder
|
||||
|
||||
internal fun shouldDisableCache(source: NativeVideoPlayerSource): Boolean
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Memory management**: Registry holds strong references to plugins; plugins get weak references to players/views.
|
||||
- **Unregister plugins**: Use unregistration to prevent memory leaks.
|
||||
- **Performance**: Minimize work in notification handlers. Cache expensive operations. Be mindful of plugin order.
|
||||
|
||||
19
docs/docs/projects.md
Normal file
19
docs/docs/projects.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Useful Projects
|
||||
description: React Native Video Useful Projects
|
||||
sidebar_class_name: hidden
|
||||
---
|
||||
# Useful Projects
|
||||
|
||||
This page lists open-source projects that can be helpful for your player implementation. <br/>
|
||||
If you have a project that could benefit other users, feel free to open a PR to add it here.
|
||||
|
||||
## Our (TheWidlarzGroup) Libraries
|
||||
- [react-native-video-player](https://github.com/TheWidlarzGroup/react-native-video-player): Our video player UI library.
|
||||
- [Offline Video SDK](https://www.thewidlarzgroup.com/offline-video-sdk?utm_source=rnv&utm_medium=docs&utm_id=projects_offline-video-sdk): If you're building an app that needs **offline playback** (e.g., downloading HLS videos, subtitles, audio tracks, or DRM-protected content), check out our commercial Offline Video SDK. It integrates with `react-native-video` and is available with a [free trial](https://sdk.thewidlarzgroup.com/signup?utm_source=rnv&utm_medium=docs&utm_id=projects_start-trial-offline-video-sdk). To get started quickly, you can clone our [Offline Video Starter Project](https://github.com/TheWidlarzGroup/react-native-offline-video-starter?utm_source=rnv&utm_medium=docs&utm_id=projects_offline-video-starter), which includes a ready-to-run example app demonstrating offline playback, multi-audio, subtitles, and DRM setup.
|
||||
|
||||
## Community Libraries
|
||||
- [react-native-corner-video](https://github.com/Lg0gs/react-native-corner-video): A floating video player.
|
||||
- [react-native-track-player](https://github.com/doublesymmetry/react-native-track-player): A toolbox for audio playback.
|
||||
- [react-native-video-controls](https://github.com/itsnubix/react-native-video-controls): A video player UI.
|
||||
- [react-native-media-console](https://github.com/criszz77/react-native-media-console): An updated version of react-native-video-controls, rewritten in TypeScript.
|
||||
176
docs/docs/updating.md
Normal file
176
docs/docs/updating.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: Updating
|
||||
description: React Native Video Updating Guide
|
||||
sidebar_class_name: hidden
|
||||
---
|
||||
|
||||
## Upgrading from react-native-video v6 to v7
|
||||
|
||||
Version 7 of `react-native-video` introduces a significant architectural shift, separating the video player logic from the UI rendering. This change unlocks new capabilities like video preloading and a more intuitive, hook-based API. This guide will walk you through the necessary steps to migrate your application from v6 to v7.
|
||||
|
||||
### Key Changes in v7
|
||||
|
||||
The most substantial change in v7 is the move from a monolithic `<Video>` component to a more modular approach with two distinct components:
|
||||
|
||||
* **`VideoPlayer`**: A new class that manages the player's state and playback logic. It is not a UI component.
|
||||
* **`VideoView`**: A UI component responsible for rendering the video on the screen. It takes a `VideoPlayer` instance as a prop.
|
||||
* **`useVideoPlayer` hook**: The recommended way to create and manage the lifecycle of a `VideoPlayer` instance within a functional component. It automatically handles the creation and cleanup of the player.
|
||||
|
||||
### New Dependency
|
||||
`react-native-video` v7 is now built on top of [`react-native-nitro-modules`](https://nitro.margelo.com/docs/what-is-nitro) framework. This means that you need to install the Nitro framework to use `react-native-video` v7.
|
||||
|
||||
### Step-by-Step Migration Guide
|
||||
|
||||
#### 1. Installation
|
||||
|
||||
First, update the `react-native-video` package to the latest v7 release:
|
||||
|
||||
```bash
|
||||
npm install react-native-video@next --save
|
||||
```
|
||||
|
||||
Then, install the pods for iOS:
|
||||
|
||||
```bash
|
||||
cd ios && pod install
|
||||
```
|
||||
|
||||
#### 2. Updating Your Component
|
||||
|
||||
The core of the migration involves replacing the `<Video>` component with the new `useVideoPlayer` hook and `<VideoView>` component.
|
||||
|
||||
**v6 Implementation:**
|
||||
|
||||
```jsx
|
||||
import React, { useRef } from 'react';
|
||||
import Video from 'react-native-video';
|
||||
|
||||
const VideoPlayerV6 = () => {
|
||||
const videoRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Video
|
||||
source={{ uri: 'https://www.w3schools.com/html/mov_bbb.mp4' }}
|
||||
ref={videoRef}
|
||||
style={{ width: 300, height: 200 }}
|
||||
controls={true}
|
||||
onLoad={() => console.log('Video loaded')}
|
||||
onProgress={(data) => console.log('Progress:', data.currentTime)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**v7 Implementation:**
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { useVideoPlayer, VideoView, useEvent } from 'react-native-video';
|
||||
|
||||
const VideoPlayerV7 = () => {
|
||||
const player = useVideoPlayer({
|
||||
source: {
|
||||
uri: 'https://www.w3schools.com/html/mov_bbb.mp4',
|
||||
},
|
||||
});
|
||||
|
||||
useEvent(player, 'onLoad', () => {
|
||||
console.log('Video loaded');
|
||||
});
|
||||
|
||||
useEvent(player, 'onProgress', (data) => {
|
||||
console.log('Progress:', data.currentTime);
|
||||
});
|
||||
|
||||
return (
|
||||
<VideoView
|
||||
player={player}
|
||||
style={{ width: 300, height: 200 }}
|
||||
controls={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Prop and Method Migration
|
||||
|
||||
Many props and methods from the v6 `<Video>` component have been moved to the `VideoPlayer` instance in v7.
|
||||
|
||||
#### Common Props
|
||||
|
||||
| v6 Prop (`<Video>`) | v7 Equivalent (`VideoPlayer` properties) | Notes |
|
||||
| :--- | :--- | :--- |
|
||||
| `source` | `source` property in `useVideoPlayer` config | The structure of the source object remains largely the same. |
|
||||
| `paused` | `paused` property on the `VideoPlayer` instance | Can be controlled via `player.pause()` and `player.play()`. |
|
||||
| `muted` | `muted` property on the `VideoPlayer` instance | `player.muted = true/false;` |
|
||||
| `volume` | `volume` property on the `VideoPlayer` instance | `player.volume = 0.5;` |
|
||||
| `rate` | `rate` property on the `VideoPlayer` instance | `player.rate = 1.5;` |
|
||||
| `loop` | `loop` property on the `VideoPlayer` instance | `player.loop = true;` |
|
||||
| `resizeMode` | `resizeMode` prop on `<VideoView>` | This remains a prop on the UI component. |
|
||||
| `controls` | `controls` prop on `<VideoView>` | This also remains on the UI component. |
|
||||
|
||||
see [VideoPlayer](./player/player.md) for more details.
|
||||
|
||||
#### Methods
|
||||
|
||||
Imperative methods previously called on the `<Video>` component's ref are now methods on the `VideoPlayer` instance.
|
||||
|
||||
| v6 Method (`videoRef.current`) | v7 Equivalent (`player`) |
|
||||
| :--- | :--- |
|
||||
| `seek(time)` | `player.seekTo(time)` |
|
||||
| `presentFullscreenPlayer()` | `videoViewRef.current.enterFullscreen()` | Fullscreen is now managed by the `VideoView` ref. |
|
||||
| `dismissFullscreenPlayer()` | `videoViewRef.current.exitFullscreen()` | |
|
||||
| `pause()` | `player.pause()` | |
|
||||
| `resume()` | `player.play()` | |
|
||||
|
||||
see [VideoPlayer](./player/player.md) for more details.
|
||||
|
||||
### Event Handling
|
||||
|
||||
In v7, event handling is standardized through the `useEvent` hook or by directly assigning callbacks to the `VideoPlayer` instance. The `useEvent` hook is recommended as it automatically handles listener cleanup.
|
||||
|
||||
**v6 Event Handling:**
|
||||
|
||||
```jsx
|
||||
<Video
|
||||
onLoad={(data) => console.log(data)}
|
||||
onProgress={(data) => console.log(data.currentTime)}
|
||||
onError={(error) => console.error(error)}
|
||||
/>
|
||||
```
|
||||
|
||||
**v7 Event Handling with `useEvent`:**
|
||||
|
||||
```jsx
|
||||
import { useVideoPlayer, VideoView, useEvent } from 'react-native-video';
|
||||
|
||||
const MyPlayer = () => {
|
||||
const player = useVideoPlayer({ source: { uri: '...' } });
|
||||
|
||||
useEvent(player, 'onLoad', (data) => console.log(data));
|
||||
useEvent(player, 'onProgress', (data) => console.log(data.currentTime));
|
||||
useEvent(player, 'onError', (error) => console.error(error.code, error.message));
|
||||
|
||||
return <VideoView player={player} />;
|
||||
}
|
||||
```
|
||||
|
||||
**Directly assigning callbacks in v7:**
|
||||
|
||||
```jsx
|
||||
const player = useVideoPlayer('https://example.com/video.mp4', (_player) => {
|
||||
_player.onLoad = (data) => {
|
||||
console.log('Video loaded! Duration:', data.duration);
|
||||
};
|
||||
_player.onError = (error) => {
|
||||
console.error('Player Error:', error.code, error.message);
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
### Benefits of the New Architecture
|
||||
|
||||
* **Preloading**: You can create a `VideoPlayer` instance and begin loading a video before it's visible in the UI. When you're ready to display it, simply pass the player instance to a `<VideoView>`.
|
||||
* **Improved Performance**: Separating the player logic from the UI rendering can lead to better performance and a more responsive application.
|
||||
* **Cleaner API**: The hook-based API simplifies player management and reduces boilerplate code, especially for handling the player's lifecycle.
|
||||
* **Full New Architecture Support**: Version 7 fully embraces React Native's New Architecture, ensuring better performance and consistency.
|
||||
67
docs/docs/use-in-third-party-library.md
Normal file
67
docs/docs/use-in-third-party-library.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Use in Third Party Library
|
||||
description: How to use React Native Video in a third party library
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Use in Third Party Library
|
||||
|
||||
You can use React Native Video in your third party library either as a dependency if you want to have specific version of the library or as a peer dependency if you want to version selection to be handled by the consumer of the library.
|
||||
|
||||
## In JS
|
||||
|
||||
Add `react-native-video` as a dependency or peer dependency.
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"dependencies": {
|
||||
"react-native-video": "latest"
|
||||
}
|
||||
// OR
|
||||
"peerDependencies": {
|
||||
"react-native-video": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And then you can import it in your code.
|
||||
|
||||
```ts
|
||||
import { VideoPlayer } from 'react-native-video';
|
||||
|
||||
const player = new VideoPlayer({ uri: 'https://www.example.com/video.mp4' });
|
||||
|
||||
player.play();
|
||||
```
|
||||
|
||||
## In Native
|
||||
|
||||
### iOS
|
||||
Add `ReactNativeVideo` as a dependency in your `*.podspec` file.
|
||||
|
||||
```ruby title="*.podspec"
|
||||
Pod::Spec.new do |s|
|
||||
// ...
|
||||
|
||||
s.dependency 'ReactNativeVideo'
|
||||
end
|
||||
```
|
||||
|
||||
### Android
|
||||
|
||||
Add `:react-native-video` and `:react-native-nitro-modules` as a dependency in your `build.gradle` file. Also you will need to add `androidx.media3` dependencies. to use player and source in your library.
|
||||
|
||||
```groovy title="build.gradle"
|
||||
// ...
|
||||
|
||||
dependencies {
|
||||
// ...
|
||||
|
||||
implementation project(':react-native-video')
|
||||
implementation project(':react-native-nitro-modules')
|
||||
|
||||
implementation "androidx.media3:media3-common:1.4.1"
|
||||
implementation "androidx.media3:media3-exoplayer:1.4.1"
|
||||
}
|
||||
```
|
||||
|
||||
107
docs/docs/video-view.md
Normal file
107
docs/docs/video-view.md
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
sidebar_label: VideoView
|
||||
description: React Native Video VideoView Component
|
||||
---
|
||||
|
||||
# VideoView Component
|
||||
|
||||
The `VideoView` component is responsible for rendering the video content managed by a `VideoPlayer` instance onto the screen. It also provides UI functionalities like native controls, fullscreen, and picture-in-picture mode.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
To use `VideoView`, you need to pass a `VideoPlayer` instance to its `player` prop.
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { VideoPlayer, VideoView } from 'react-native-video';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
const App = () => {
|
||||
const player = useVideoPlayer('https://example.com/video.mp4', (_player) => {
|
||||
// This is optional setup function that will be called when the player is created.
|
||||
_player.play();
|
||||
});
|
||||
|
||||
return (
|
||||
<VideoView
|
||||
style={styles.video}
|
||||
player={player}
|
||||
controls={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
video: {
|
||||
width: '100%',
|
||||
height: 200,
|
||||
},
|
||||
});
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Required | Default | Description |
|
||||
|------|------|----------|---------|-------------|
|
||||
| `player` | `VideoPlayer` | Yes | - | The `VideoPlayer` instance that manages the video to be displayed. |
|
||||
| `style` | `ViewStyle` | No | - | Standard React Native styles to control the layout and appearance of the `VideoView`. |
|
||||
| `controls` | `boolean` | No | `false` | Whether to show the native video playback controls (play/pause, seek bar, volume, etc.). |
|
||||
| `pictureInPicture` | `boolean` | No | `false` | Whether to enable and show the picture-in-picture (PiP) button in the native controls (if supported by the platform and controls are visible). |
|
||||
| `autoEnterPictureInPicture` | `boolean` | No | `false` | Whether the video should automatically enter PiP mode when it starts playing and the app is backgrounded (behavior might vary by platform). |
|
||||
| `resizeMode` | `'contain' \| 'cover' \| 'stretch' \| 'none'` | No | `'none'` | How the video should be resized to fit the view. |
|
||||
| `keepScreenAwake` | `boolean` | No | `true` | Whether to keep the device screen awake while the video view is mounted. |
|
||||
|
||||
## Events
|
||||
|
||||
`VideoView` also accepts several event callback props related to UI state changes:
|
||||
|
||||
| Event | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `onPictureInPictureChange?` | `(event: { isActive: boolean }) => void` | Fired when the picture-in-picture mode starts or stops. |
|
||||
| `onFullscreenChange?` | `(event: { isFullscreen: boolean }) => void` | Fired when the fullscreen mode starts or stops. |
|
||||
| `willEnterFullscreen?` | `() => void` | Fired just before the view enters fullscreen mode. |
|
||||
| `willExitFullscreen?` | `() => void` | Fired just before the view exits fullscreen mode. |
|
||||
| `willEnterPictureInPicture?` | `() => void` | Fired just before the view enters picture-in-picture mode. |
|
||||
| `willExitPictureInPicture?` | `() => void` | Fired just before the view exits picture-in-picture mode. |
|
||||
|
||||
These can be used to update your component's state or UI in response to these changes.
|
||||
|
||||
```tsx
|
||||
<VideoView
|
||||
player={player}
|
||||
onFullscreenChange={({ isFullscreen }) => {
|
||||
console.log(isFullscreen ? 'Entered fullscreen' : 'Exited fullscreen');
|
||||
}}
|
||||
onPictureInPictureChange={({ isActive }) => {
|
||||
console.log(isActive ? 'PiP active' : 'PiP inactive');
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Refs and Imperative Methods
|
||||
|
||||
You can obtain a ref to the `VideoView` component to call imperative methods:
|
||||
|
||||
```tsx
|
||||
const videoViewRef = React.useRef<VideoViewRef>(null);
|
||||
|
||||
// ...
|
||||
|
||||
<VideoView ref={videoViewRef} player={player} />
|
||||
|
||||
// Later, you can call methods like:
|
||||
videoViewRef.current?.enterFullscreen();
|
||||
```
|
||||
|
||||
Available methods on the `VideoViewRef`:
|
||||
|
||||
| Method | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `enterFullscreen()` | `() => void` | Programmatically requests the video view to enter fullscreen mode. |
|
||||
| `exitFullscreen()` | `() => void` | Programmatically requests the video view to exit fullscreen mode. |
|
||||
| `enterPictureInPicture()` | `() => void` | Programmatically requests the video view to enter picture-in-picture mode. |
|
||||
| `exitPictureInPicture()` | `() => void` | Programmatically requests the video view to exit picture-in-picture mode. |
|
||||
| `canEnterPictureInPicture()` | `() => boolean` | Checks if picture-in-picture mode is currently available and supported. Returns `true` if PiP can be entered, `false` otherwise. |
|
||||
237
docs/docusaurus.config.ts
Normal file
237
docs/docusaurus.config.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import type * as Preset from '@docusaurus/preset-classic';
|
||||
import type { Config } from '@docusaurus/types';
|
||||
import { themes as prismThemes } from 'prism-react-renderer';
|
||||
|
||||
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
|
||||
|
||||
const config: Config = {
|
||||
title: 'React Native Video',
|
||||
tagline: 'React Native Video - Video player for React Native',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
url: 'https://docs.thewidlarzgroup.com',
|
||||
baseUrl: '/react-native-video',
|
||||
|
||||
organizationName: 'TheWidlarzGroup',
|
||||
projectName: 'react-native-video',
|
||||
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
|
||||
future: {
|
||||
experimental_faster: true,
|
||||
v4: true
|
||||
},
|
||||
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
{
|
||||
docs: {
|
||||
sidebarPath: './sidebars.ts',
|
||||
lastVersion: '6.x',
|
||||
includeCurrentVersion: true,
|
||||
versions: {
|
||||
current: {
|
||||
label: 'v7 Alpha',
|
||||
path: 'v7',
|
||||
banner: 'none',
|
||||
},
|
||||
'6.x': {
|
||||
label: 'v6',
|
||||
path: 'v6',
|
||||
},
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
|
||||
headTags: [
|
||||
{
|
||||
tagName: 'link',
|
||||
attributes: {
|
||||
rel: 'preconnect',
|
||||
href: 'https://fonts.googleapis.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
tagName: 'link',
|
||||
attributes: {
|
||||
rel: 'preconnect',
|
||||
href: 'https://fonts.gstatic.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
tagName: 'link',
|
||||
attributes: {
|
||||
rel: 'stylesheet',
|
||||
href: 'https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
image: 'img/twg-social-card.png',
|
||||
navbar: {
|
||||
title: 'React Native Video',
|
||||
logo: {
|
||||
alt: 'React Native Video Logo',
|
||||
style: {
|
||||
width: '60px',
|
||||
height: '40px',
|
||||
marginRight: '10px',
|
||||
transform: 'translateY(-4px)',
|
||||
},
|
||||
src: 'img/twg-logo.png',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'docsSidebar',
|
||||
position: 'left',
|
||||
label: 'Documentation',
|
||||
},
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
versions: {
|
||||
current: {label: 'v7 Alpha'},
|
||||
'6.x': {label: 'v6'},
|
||||
},
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/TheWidlarzGroup/react-native-video',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://www.thewidlarzgroup.com/react-native-video?utm_source=rnv&utm_medium=docs&utm_campaign=navbar&utm_id=offer-button',
|
||||
label: 'Offer',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
type: 'search',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
docs: {
|
||||
sidebar: {
|
||||
hideable: false,
|
||||
},
|
||||
versionPersistence: 'localStorage',
|
||||
},
|
||||
footer: {
|
||||
style: 'light',
|
||||
copyright: `Built With ❤️ By TheWidlarzGroup & React Native Video Community`,
|
||||
},
|
||||
colorMode: {
|
||||
defaultMode: 'dark',
|
||||
disableSwitch: true,
|
||||
respectPrefersColorScheme: false,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.oneLight,
|
||||
darkTheme: prismThemes.oneDark,
|
||||
},
|
||||
} satisfies Preset.ThemeConfig,
|
||||
|
||||
plugins: [
|
||||
require.resolve('docusaurus-lunr-search'),
|
||||
[
|
||||
'docusaurus-plugin-typedoc',
|
||||
{
|
||||
name: 'API Reference',
|
||||
entryPoints: ['../packages/react-native-video/src'],
|
||||
exclude: "../packages/react-native-video/src/index.ts",
|
||||
tsconfig: '../packages/react-native-video/tsconfig.json',
|
||||
out: './docs/api-reference',
|
||||
watch: process.env.TYPEDOC_WATCH,
|
||||
excludePrivate: true,
|
||||
excludeProtected: true,
|
||||
excludeExternals: true,
|
||||
excludeInternal: true,
|
||||
readme: "none",
|
||||
sidebar: {
|
||||
autoConfiguration: false,
|
||||
},
|
||||
parametersFormat: "table",
|
||||
enumMembersFormat: "table",
|
||||
useCodeBlocks: true,
|
||||
},
|
||||
],
|
||||
// LLMs txt generation for v6
|
||||
[
|
||||
'docusaurus-plugin-llms',
|
||||
{
|
||||
id: 'llms-v6',
|
||||
generateLLMsTxt: false,
|
||||
generateLLMsFullTxt: false,
|
||||
docsDir: "versioned_docs/version-6.x",
|
||||
pathTransformation: {
|
||||
ignorePaths: ['docs'],
|
||||
addPaths: ['react-native-video/docs/v6']
|
||||
},
|
||||
version: '6.x.x',
|
||||
customLLMFiles: [
|
||||
{
|
||||
filename: 'llms-v6.txt',
|
||||
title: 'React Native Video v6 Documentation',
|
||||
description: 'Complete documentation for React Native Video v6',
|
||||
includePatterns: ['**/*.md', '**/*.mdx'],
|
||||
fullContent: false,
|
||||
},
|
||||
{
|
||||
filename: 'llms-v6-full.txt',
|
||||
title: 'React Native Video v6 Documentation',
|
||||
description: 'Complete documentation for React Native Video v6',
|
||||
includePatterns: ['**/*.md', '**/*.mdx'],
|
||||
fullContent: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
// LLMs txt generation for v7
|
||||
[
|
||||
'docusaurus-plugin-llms',
|
||||
{
|
||||
id: 'llms-v7',
|
||||
generateLLMsTxt: false,
|
||||
generateLLMsFullTxt: false,
|
||||
docsDir: "docs",
|
||||
pathTransformation: {
|
||||
ignorePaths: ['docs'],
|
||||
addPaths: ['react-native-video/docs/v7']
|
||||
},
|
||||
version: '7.x.x',
|
||||
customLLMFiles: [
|
||||
{
|
||||
filename: 'llms-v7.txt',
|
||||
title: 'React Native Video v7 Documentation',
|
||||
description: 'Complete documentation for React Native Video v7',
|
||||
includePatterns: ['docs/**/*.md'],
|
||||
fullContent: false,
|
||||
},
|
||||
{
|
||||
filename: 'llms-v7-full.txt',
|
||||
title: 'React Native Video v7 Documentation',
|
||||
description: 'Complete documentation for React Native Video v7',
|
||||
includePatterns: ['docs/**/*.md'],
|
||||
fullContent: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
5
docs/next-env.d.ts
vendored
5
docs/next-env.d.ts
vendored
@@ -1,5 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
@@ -1,28 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const withNextra = require('nextra')({
|
||||
theme: 'nextra-theme-docs',
|
||||
themeConfig: './theme.config.jsx',
|
||||
});
|
||||
|
||||
let assetPrefix = '';
|
||||
let basePath = '';
|
||||
|
||||
// If we're in a GitHub Action, we need to set the assetPrefix and basePath
|
||||
// to add repo_name to the path.
|
||||
// eg. https://<organization>.github.io/<repo_name>
|
||||
if (process.env.GITHUB_ACTIONS) {
|
||||
const repo_name = 'react-native-video';
|
||||
|
||||
assetPrefix = `/${repo_name}/`;
|
||||
basePath = `/${repo_name}`;
|
||||
}
|
||||
|
||||
module.exports = withNextra({
|
||||
output: 'export',
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
assetPrefix,
|
||||
basePath,
|
||||
});
|
||||
@@ -1,21 +1,51 @@
|
||||
{
|
||||
"name": "react-native-video-docs",
|
||||
"version": "0.0.1",
|
||||
"name": "@react-native-video/docs",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Documentation for react-native-video",
|
||||
"scripts": {
|
||||
"dev": "bun next dev",
|
||||
"build": "bun next build"
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "TYPEDOC_WATCH=true docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^13.5.4",
|
||||
"nextra": "^2.13.2",
|
||||
"nextra-theme-docs": "^2.13.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "^5.4.2"
|
||||
"@docusaurus/core": "^3.8.1",
|
||||
"@docusaurus/faster": "^3.8.1",
|
||||
"@docusaurus/preset-classic": "^3.8.1",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"docusaurus-lunr-search": "^3.6.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
"@docusaurus/module-type-aliases": "^3.8.1",
|
||||
"@docusaurus/tsconfig": "^3.8.1",
|
||||
"@docusaurus/types": "^3.8.1",
|
||||
"typescript": "^5.2.2",
|
||||
"docusaurus-plugin-typedoc": "^1.4.0",
|
||||
"docusaurus-plugin-llms": "^0.1.5"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 3 chrome version",
|
||||
"last 3 firefox version",
|
||||
"last 5 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"index": "Introduction",
|
||||
"installation": "Installation",
|
||||
"component": "API",
|
||||
"other": "Other",
|
||||
"separator_versions": {
|
||||
"type": "separator",
|
||||
"title": ""
|
||||
},
|
||||
"updating": "Updating",
|
||||
"changelog": {
|
||||
"title": "Changelog",
|
||||
"newWindow": true,
|
||||
"href": "https://github.com/react-native-video/react-native-video/blob/master/CHANGELOG.md"
|
||||
},
|
||||
"separator_community": {
|
||||
"type": "separator",
|
||||
"title": ""
|
||||
},
|
||||
"projects": "Useful projects"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"props": "Properties",
|
||||
"drm": "DRM",
|
||||
"ads": "Ads",
|
||||
"events": "Events",
|
||||
"methods": "Methods"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user