VEX-5682: iOS Swift Conversion (#11)
Converts ios implementation from objective-c to swift.
This commit is contained in:
parent
3bcf2c6061
commit
8b75438148
3
.gitignore
vendored
3
.gitignore
vendored
@ -29,6 +29,9 @@ project.xcworkspace
|
|||||||
.gradle
|
.gradle
|
||||||
local.properties
|
local.properties
|
||||||
*.hprof
|
*.hprof
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.classpath
|
||||||
|
|
||||||
# node.js
|
# node.js
|
||||||
#
|
#
|
||||||
|
@ -2093,6 +2093,11 @@ ee-first@1.1.1:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
|
||||||
|
eme-encryption-scheme-polyfill@^2.0.1:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/eme-encryption-scheme-polyfill/-/eme-encryption-scheme-polyfill-2.0.3.tgz#2ca6e06480e06cceb5e50efd27943ac46c959878"
|
||||||
|
integrity sha512-44CNFMsqzHdKHrzWxlS7xZ8KUHn5XutBqpmCuWzNIynmAyFInHrrD3ozv/RvK9ZhgV6QY6Easx8EWAmxteNodg==
|
||||||
|
|
||||||
emoji-regex@^7.0.1:
|
emoji-regex@^7.0.1:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
||||||
@ -5160,7 +5165,7 @@ prompts@^2.0.1:
|
|||||||
kleur "^3.0.3"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.3"
|
sisteransi "^1.0.3"
|
||||||
|
|
||||||
prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2:
|
prop-types@^15.6.2, prop-types@^15.7.2:
|
||||||
version "15.7.2"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||||
@ -5238,11 +5243,11 @@ react-is@^16.8.4, react-is@^16.8.6:
|
|||||||
integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==
|
integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==
|
||||||
|
|
||||||
"react-native-video@file:../..":
|
"react-native-video@file:../..":
|
||||||
version "5.0.1"
|
version "5.1.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
keymirror "^0.1.1"
|
keymirror "^0.1.1"
|
||||||
prop-types "^15.5.10"
|
prop-types "^15.7.2"
|
||||||
shaka-player "^2.4.4"
|
shaka-player "^2.5.9"
|
||||||
|
|
||||||
react-native@0.60.5:
|
react-native@0.60.5:
|
||||||
version "0.60.5"
|
version "0.60.5"
|
||||||
@ -5774,10 +5779,12 @@ setprototypeof@1.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||||
|
|
||||||
shaka-player@^2.4.4:
|
shaka-player@^2.5.9:
|
||||||
version "2.5.2"
|
version "2.5.23"
|
||||||
resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.2.tgz#3e639f8f5dfdb1a0afff2ff1f87cddf481f93fe4"
|
resolved "https://registry.yarnpkg.com/shaka-player/-/shaka-player-2.5.23.tgz#db92d1c6cf2314f0180a2cec11b0e2f2560336f5"
|
||||||
integrity sha512-gpRfXVLAZi33kw9Egop18MkZ/EVRS0soeN6ocR+Btq/J5IoCC56MxwwHzAGna+PgBW9Ox458HRefJ6HB0BoADA==
|
integrity sha512-3MC9k0OXJGw8AZ4n/ZNCZS2yDxx+3as5KgH6Tx4Q5TRboTBBCu6dYPI5vp1DxKeyU12MBN1Zcbs7AKzXv2EnCg==
|
||||||
|
dependencies:
|
||||||
|
eme-encryption-scheme-polyfill "^2.0.1"
|
||||||
|
|
||||||
shebang-command@^1.2.0:
|
shebang-command@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
|
@ -7,14 +7,12 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
D1107C0A2110259000073188 /* UIView+FindUIViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C032110259000073188 /* UIView+FindUIViewController.m */; };
|
0177D39A27170A7A00F5BE18 /* RCTVideoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39227170A7A00F5BE18 /* RCTVideoManager.swift */; };
|
||||||
D1107C0B2110259000073188 /* UIView+FindUIViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C032110259000073188 /* UIView+FindUIViewController.m */; };
|
0177D39B27170A7A00F5BE18 /* UIView+FindUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39327170A7A00F5BE18 /* UIView+FindUIViewController.swift */; };
|
||||||
D1107C0C2110259000073188 /* RCTVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C052110259000073188 /* RCTVideo.m */; };
|
0177D39C27170A7A00F5BE18 /* RCTVideoPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39427170A7A00F5BE18 /* RCTVideoPlayerViewController.swift */; };
|
||||||
D1107C0D2110259000073188 /* RCTVideo.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C052110259000073188 /* RCTVideo.m */; };
|
0177D39D27170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39627170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift */; };
|
||||||
D1107C0E2110259000073188 /* RCTVideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C062110259000073188 /* RCTVideoManager.m */; };
|
0177D39E27170A7A00F5BE18 /* RCTVideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39727170A7A00F5BE18 /* RCTVideoManager.m */; };
|
||||||
D1107C0F2110259000073188 /* RCTVideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C062110259000073188 /* RCTVideoManager.m */; };
|
0177D39F27170A7A00F5BE18 /* RCTVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177D39927170A7A00F5BE18 /* RCTVideo.swift */; };
|
||||||
D1107C102110259000073188 /* RCTVideoPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C082110259000073188 /* RCTVideoPlayerViewController.m */; };
|
|
||||||
D1107C112110259000073188 /* RCTVideoPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D1107C082110259000073188 /* RCTVideoPlayerViewController.m */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
@ -39,17 +37,18 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
01450CB5271D5738005D8F6B /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
01489050272001A100E69940 /* DataStructures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DataStructures; path = Video/DataStructures; sourceTree = "<group>"; };
|
||||||
|
01489051272001A100E69940 /* Features */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Features; path = Video/Features; sourceTree = "<group>"; };
|
||||||
|
0177D39227170A7A00F5BE18 /* RCTVideoManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RCTVideoManager.swift; path = Video/RCTVideoManager.swift; sourceTree = "<group>"; };
|
||||||
|
0177D39327170A7A00F5BE18 /* UIView+FindUIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+FindUIViewController.swift"; path = "Video/UIView+FindUIViewController.swift"; sourceTree = "<group>"; };
|
||||||
|
0177D39427170A7A00F5BE18 /* RCTVideoPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RCTVideoPlayerViewController.swift; path = Video/RCTVideoPlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
0177D39527170A7A00F5BE18 /* RCTSwiftLog */ = {isa = PBXFileReference; lastKnownFileType = folder; name = RCTSwiftLog; path = Video/RCTSwiftLog; sourceTree = "<group>"; };
|
||||||
|
0177D39627170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RCTVideoPlayerViewControllerDelegate.swift; path = Video/RCTVideoPlayerViewControllerDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
0177D39727170A7A00F5BE18 /* RCTVideoManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RCTVideoManager.m; path = Video/RCTVideoManager.m; sourceTree = "<group>"; };
|
||||||
|
0177D39827170A7A00F5BE18 /* RCTVideo-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTVideo-Bridging-Header.h"; path = "Video/RCTVideo-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
0177D39927170A7A00F5BE18 /* RCTVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RCTVideo.swift; path = Video/RCTVideo.swift; sourceTree = "<group>"; };
|
||||||
134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
641E28441F0EEC8500443AF6 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
D1107C012110259000073188 /* RCTVideoPlayerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTVideoPlayerViewController.h; path = Video/RCTVideoPlayerViewController.h; sourceTree = "<group>"; };
|
|
||||||
D1107C022110259000073188 /* RCTVideoPlayerViewControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTVideoPlayerViewControllerDelegate.h; path = Video/RCTVideoPlayerViewControllerDelegate.h; sourceTree = "<group>"; };
|
|
||||||
D1107C032110259000073188 /* UIView+FindUIViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+FindUIViewController.m"; path = "Video/UIView+FindUIViewController.m"; sourceTree = "<group>"; };
|
|
||||||
D1107C042110259000073188 /* UIView+FindUIViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+FindUIViewController.h"; path = "Video/UIView+FindUIViewController.h"; sourceTree = "<group>"; };
|
|
||||||
D1107C052110259000073188 /* RCTVideo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RCTVideo.m; path = Video/RCTVideo.m; sourceTree = "<group>"; };
|
|
||||||
D1107C062110259000073188 /* RCTVideoManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RCTVideoManager.m; path = Video/RCTVideoManager.m; sourceTree = "<group>"; };
|
|
||||||
D1107C072110259000073188 /* RCTVideo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTVideo.h; path = Video/RCTVideo.h; sourceTree = "<group>"; };
|
|
||||||
D1107C082110259000073188 /* RCTVideoPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RCTVideoPlayerViewController.m; path = Video/RCTVideoPlayerViewController.m; sourceTree = "<group>"; };
|
|
||||||
D1107C092110259000073188 /* RCTVideoManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTVideoManager.h; path = Video/RCTVideoManager.h; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -88,18 +87,19 @@
|
|||||||
58B511D21A9E6C8500147676 = {
|
58B511D21A9E6C8500147676 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D1107C072110259000073188 /* RCTVideo.h */,
|
01489050272001A100E69940 /* DataStructures */,
|
||||||
D1107C052110259000073188 /* RCTVideo.m */,
|
01489051272001A100E69940 /* Features */,
|
||||||
D1107C092110259000073188 /* RCTVideoManager.h */,
|
0177D39527170A7A00F5BE18 /* RCTSwiftLog */,
|
||||||
D1107C062110259000073188 /* RCTVideoManager.m */,
|
0177D39927170A7A00F5BE18 /* RCTVideo.swift */,
|
||||||
D1107C012110259000073188 /* RCTVideoPlayerViewController.h */,
|
0177D39727170A7A00F5BE18 /* RCTVideoManager.m */,
|
||||||
D1107C082110259000073188 /* RCTVideoPlayerViewController.m */,
|
0177D39227170A7A00F5BE18 /* RCTVideoManager.swift */,
|
||||||
D1107C022110259000073188 /* RCTVideoPlayerViewControllerDelegate.h */,
|
0177D39427170A7A00F5BE18 /* RCTVideoPlayerViewController.swift */,
|
||||||
D1107C042110259000073188 /* UIView+FindUIViewController.h */,
|
0177D39627170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift */,
|
||||||
D1107C032110259000073188 /* UIView+FindUIViewController.m */,
|
0177D39327170A7A00F5BE18 /* UIView+FindUIViewController.swift */,
|
||||||
|
0177D39827170A7A00F5BE18 /* RCTVideo-Bridging-Header.h */,
|
||||||
134814211AA4EA7D00B7C361 /* Products */,
|
134814211AA4EA7D00B7C361 /* Products */,
|
||||||
641E28441F0EEC8500443AF6 /* libRCTVideo.a */,
|
|
||||||
49E995712048B4CE00EA7890 /* Frameworks */,
|
49E995712048B4CE00EA7890 /* Frameworks */,
|
||||||
|
01450CB5271D5738005D8F6B /* libRCTVideo.a */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@ -137,7 +137,7 @@
|
|||||||
);
|
);
|
||||||
name = "RCTVideo-tvOS";
|
name = "RCTVideo-tvOS";
|
||||||
productName = "RCTVideo-tvOS";
|
productName = "RCTVideo-tvOS";
|
||||||
productReference = 641E28441F0EEC8500443AF6 /* libRCTVideo.a */;
|
productReference = 01450CB5271D5738005D8F6B /* libRCTVideo.a */;
|
||||||
productType = "com.apple.product-type.library.static";
|
productType = "com.apple.product-type.library.static";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
@ -151,9 +151,11 @@
|
|||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
58B511DA1A9E6C8500147676 = {
|
58B511DA1A9E6C8500147676 = {
|
||||||
CreatedOnToolsVersion = 6.1.1;
|
CreatedOnToolsVersion = 6.1.1;
|
||||||
|
LastSwiftMigration = 1300;
|
||||||
};
|
};
|
||||||
641E28431F0EEC8500443AF6 = {
|
641E28431F0EEC8500443AF6 = {
|
||||||
CreatedOnToolsVersion = 8.3.3;
|
CreatedOnToolsVersion = 8.3.3;
|
||||||
|
LastSwiftMigration = 1300;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -163,6 +165,7 @@
|
|||||||
developmentRegion = English;
|
developmentRegion = English;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
English,
|
||||||
en,
|
en,
|
||||||
);
|
);
|
||||||
mainGroup = 58B511D21A9E6C8500147676;
|
mainGroup = 58B511D21A9E6C8500147676;
|
||||||
@ -181,10 +184,12 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D1107C0A2110259000073188 /* UIView+FindUIViewController.m in Sources */,
|
0177D39D27170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift in Sources */,
|
||||||
D1107C102110259000073188 /* RCTVideoPlayerViewController.m in Sources */,
|
0177D39C27170A7A00F5BE18 /* RCTVideoPlayerViewController.swift in Sources */,
|
||||||
D1107C0E2110259000073188 /* RCTVideoManager.m in Sources */,
|
0177D39B27170A7A00F5BE18 /* UIView+FindUIViewController.swift in Sources */,
|
||||||
D1107C0C2110259000073188 /* RCTVideo.m in Sources */,
|
0177D39F27170A7A00F5BE18 /* RCTVideo.swift in Sources */,
|
||||||
|
0177D39E27170A7A00F5BE18 /* RCTVideoManager.m in Sources */,
|
||||||
|
0177D39A27170A7A00F5BE18 /* RCTVideoManager.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -192,10 +197,6 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D1107C0B2110259000073188 /* UIView+FindUIViewController.m in Sources */,
|
|
||||||
D1107C112110259000073188 /* RCTVideoPlayerViewController.m in Sources */,
|
|
||||||
D1107C0F2110259000073188 /* RCTVideoManager.m in Sources */,
|
|
||||||
D1107C0D2110259000073188 /* RCTVideo.m in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -280,30 +281,39 @@
|
|||||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||||
"$(SRCROOT)/Vendor/SPTPersistentCache/include/**",
|
"$(SRCROOT)/Vendor/SPTPersistentCache/include/**",
|
||||||
);
|
);
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
PRODUCT_NAME = RCTVideo;
|
PRODUCT_NAME = RCTVideo;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Video/RCTVideo-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
58B511F11A9E6C8500147676 /* Release */ = {
|
58B511F11A9E6C8500147676 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
HEADER_SEARCH_PATHS = (
|
HEADER_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||||
"$(SRCROOT)/Vendor/SPTPersistentCache/include/**",
|
"$(SRCROOT)/Vendor/SPTPersistentCache/include/**",
|
||||||
);
|
);
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
PRODUCT_NAME = RCTVideo;
|
PRODUCT_NAME = RCTVideo;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Video/RCTVideo-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@ -312,15 +322,19 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
PRODUCT_NAME = RCTVideo;
|
PRODUCT_NAME = RCTVideo;
|
||||||
SDKROOT = appletvos;
|
SDKROOT = appletvos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
TVOS_DEPLOYMENT_TARGET = 10.2;
|
TVOS_DEPLOYMENT_TARGET = 10.2;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@ -330,16 +344,19 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
PRODUCT_NAME = RCTVideo;
|
PRODUCT_NAME = RCTVideo;
|
||||||
SDKROOT = appletvos;
|
SDKROOT = appletvos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
TVOS_DEPLOYMENT_TARGET = 10.2;
|
TVOS_DEPLOYMENT_TARGET = 10.2;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
30
ios/Video/DataStructures/DRMParams.swift
Normal file
30
ios/Video/DataStructures/DRMParams.swift
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
struct DRMParams {
|
||||||
|
let type: String?
|
||||||
|
let licenseServer: String?
|
||||||
|
let headers: Dictionary<String,Any>?
|
||||||
|
let contentId: String?
|
||||||
|
let certificateUrl: String?
|
||||||
|
let base64Certificate: Bool?
|
||||||
|
|
||||||
|
let json: NSDictionary?
|
||||||
|
|
||||||
|
init(_ json: NSDictionary!) {
|
||||||
|
guard json != nil else {
|
||||||
|
self.json = nil
|
||||||
|
self.type = nil
|
||||||
|
self.licenseServer = nil
|
||||||
|
self.contentId = nil
|
||||||
|
self.certificateUrl = nil
|
||||||
|
self.base64Certificate = nil
|
||||||
|
self.headers = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.json = json
|
||||||
|
self.type = json["type"] as? String
|
||||||
|
self.licenseServer = json["licenseServer"] as? String
|
||||||
|
self.contentId = json["contentId"] as? String
|
||||||
|
self.certificateUrl = json["certificateUrl"] as? String
|
||||||
|
self.base64Certificate = json["base64Certificate"] as? Bool
|
||||||
|
self.headers = json["headers"] as? Dictionary<String,Any>
|
||||||
|
}
|
||||||
|
}
|
18
ios/Video/DataStructures/SelectedTrackCriteria.swift
Normal file
18
ios/Video/DataStructures/SelectedTrackCriteria.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
struct SelectedTrackCriteria {
|
||||||
|
let type: String
|
||||||
|
let value: Any?
|
||||||
|
|
||||||
|
let json: NSDictionary?
|
||||||
|
|
||||||
|
init(_ json: NSDictionary!) {
|
||||||
|
guard json != nil else {
|
||||||
|
self.json = nil
|
||||||
|
self.type = ""
|
||||||
|
self.value = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.json = json
|
||||||
|
self.type = json["type"] as? String ?? ""
|
||||||
|
self.value = json["value"]
|
||||||
|
}
|
||||||
|
}
|
25
ios/Video/DataStructures/TextTrack.swift
Normal file
25
ios/Video/DataStructures/TextTrack.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
struct TextTrack {
|
||||||
|
let type: String
|
||||||
|
let language: String
|
||||||
|
let title: String
|
||||||
|
let uri: String
|
||||||
|
|
||||||
|
let json: NSDictionary?
|
||||||
|
|
||||||
|
init(_ json: NSDictionary!) {
|
||||||
|
guard json != nil else {
|
||||||
|
self.json = nil
|
||||||
|
self.type = ""
|
||||||
|
self.language = ""
|
||||||
|
self.title = ""
|
||||||
|
self.uri = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.json = json
|
||||||
|
self.type = json["type"] as? String ?? ""
|
||||||
|
self.language = json["language"] as? String ?? ""
|
||||||
|
self.title = json["title"] as? String ?? ""
|
||||||
|
self.uri = json["uri"] as? String ?? ""
|
||||||
|
}
|
||||||
|
}
|
31
ios/Video/DataStructures/VideoSource.swift
Normal file
31
ios/Video/DataStructures/VideoSource.swift
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
struct VideoSource {
|
||||||
|
let type: String?
|
||||||
|
let uri: String?
|
||||||
|
let isNetwork: Bool
|
||||||
|
let isAsset: Bool
|
||||||
|
let shouldCache: Bool
|
||||||
|
let requestHeaders: Dictionary<String,Any>?
|
||||||
|
|
||||||
|
let json: NSDictionary?
|
||||||
|
|
||||||
|
init(_ json: NSDictionary!) {
|
||||||
|
guard json != nil else {
|
||||||
|
self.json = nil
|
||||||
|
self.type = nil
|
||||||
|
self.uri = nil
|
||||||
|
self.isNetwork = false
|
||||||
|
self.isAsset = false
|
||||||
|
self.shouldCache = false
|
||||||
|
self.requestHeaders = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.json = json
|
||||||
|
self.type = json["type"] as? String
|
||||||
|
self.uri = json["uri"] as? String
|
||||||
|
self.isNetwork = json["isNetwork"] as? Bool ?? false
|
||||||
|
self.isAsset = json["isAsset"] as? Bool ?? false
|
||||||
|
self.shouldCache = json["shouldCache"] as? Bool ?? false
|
||||||
|
self.requestHeaders = json["requestHeaders"] as? Dictionary<String,Any>
|
||||||
|
}
|
||||||
|
}
|
75
ios/Video/Features/RCTPictureInPicture.swift
Normal file
75
ios/Video/Features/RCTPictureInPicture.swift
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import AVFoundation
|
||||||
|
import AVKit
|
||||||
|
import MediaAccessibility
|
||||||
|
import React
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
#if TARGET_OS_IOS
|
||||||
|
class RCTPictureInPicture: NSObject, AVPictureInPictureControllerDelegate {
|
||||||
|
private var _onPictureInPictureStatusChanged: RCTDirectEventBlock?
|
||||||
|
private var _onRestoreUserInterfaceForPictureInPictureStop: RCTDirectEventBlock?
|
||||||
|
private var _restoreUserInterfaceForPIPStopCompletionHandler:((Bool) -> Void)? = nil
|
||||||
|
private var _pipController:AVPictureInPictureController?
|
||||||
|
private var _isActive:Bool = false
|
||||||
|
|
||||||
|
init(_ onPictureInPictureStatusChanged: @escaping RCTDirectEventBlock, _ onRestoreUserInterfaceForPictureInPictureStop: @escaping RCTDirectEventBlock) {
|
||||||
|
_onPictureInPictureStatusChanged = onPictureInPictureStatusChanged
|
||||||
|
_onRestoreUserInterfaceForPictureInPictureStop = onRestoreUserInterfaceForPictureInPictureStop
|
||||||
|
}
|
||||||
|
|
||||||
|
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
|
guard let _onPictureInPictureStatusChanged = _onPictureInPictureStatusChanged else { return }
|
||||||
|
|
||||||
|
_onPictureInPictureStatusChanged([ "isActive": NSNumber(value: true)])
|
||||||
|
}
|
||||||
|
|
||||||
|
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
|
guard let _onPictureInPictureStatusChanged = _onPictureInPictureStatusChanged else { return }
|
||||||
|
|
||||||
|
_onPictureInPictureStatusChanged([ "isActive": NSNumber(value: false)])
|
||||||
|
}
|
||||||
|
|
||||||
|
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
||||||
|
|
||||||
|
assert(_restoreUserInterfaceForPIPStopCompletionHandler == nil, "restoreUserInterfaceForPIPStopCompletionHandler was not called after picture in picture was exited.")
|
||||||
|
|
||||||
|
guard let _onRestoreUserInterfaceForPictureInPictureStop = _onRestoreUserInterfaceForPictureInPictureStop else { return }
|
||||||
|
|
||||||
|
_onRestoreUserInterfaceForPictureInPictureStop([:])
|
||||||
|
|
||||||
|
_restoreUserInterfaceForPIPStopCompletionHandler = completionHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRestoreUserInterfaceForPIPStopCompletionHandler(_ restore:Bool) {
|
||||||
|
guard let _restoreUserInterfaceForPIPStopCompletionHandler = _restoreUserInterfaceForPIPStopCompletionHandler else { return }
|
||||||
|
_restoreUserInterfaceForPIPStopCompletionHandler(restore)
|
||||||
|
self._restoreUserInterfaceForPIPStopCompletionHandler = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupPipController(_ playerLayer: AVPlayerLayer?) {
|
||||||
|
guard playerLayer != nil && AVPictureInPictureController.isPictureInPictureSupported() else { return }
|
||||||
|
// Create new controller passing reference to the AVPlayerLayer
|
||||||
|
_pipController = AVPictureInPictureController(playerLayer:playerLayer!)
|
||||||
|
_pipController?.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPictureInPicture(_ isActive:Bool) {
|
||||||
|
if _isActive == isActive {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_isActive = isActive
|
||||||
|
|
||||||
|
guard let _pipController = _pipController else { return }
|
||||||
|
|
||||||
|
if _isActive && !_pipController.isPictureInPictureActive {
|
||||||
|
DispatchQueue.main.async(execute: {
|
||||||
|
_pipController.startPictureInPicture()
|
||||||
|
})
|
||||||
|
} else if !_isActive && _pipController.isPictureInPictureActive {
|
||||||
|
DispatchQueue.main.async(execute: {
|
||||||
|
_pipController.stopPictureInPicture()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
212
ios/Video/Features/RCTPlayerObserver.swift
Normal file
212
ios/Video/Features/RCTPlayerObserver.swift
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import AVFoundation
|
||||||
|
import AVKit
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objc
|
||||||
|
protocol RCTPlayerObserverHandlerObjc {
|
||||||
|
func handleDidFailToFinishPlaying(notification:NSNotification!)
|
||||||
|
func handlePlaybackStalled(notification:NSNotification!)
|
||||||
|
func handlePlayerItemDidReachEnd(notification:NSNotification!)
|
||||||
|
// unused
|
||||||
|
// func handleAVPlayerAccess(notification:NSNotification!)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol RCTPlayerObserverHandler: RCTPlayerObserverHandlerObjc {
|
||||||
|
func handleTimeUpdate(time:CMTime)
|
||||||
|
func handleReadyForDisplay(changeObject: Any, change:NSKeyValueObservedChange<Bool>)
|
||||||
|
func handleTimeMetadataChange(playerItem:AVPlayerItem, change:NSKeyValueObservedChange<[AVMetadataItem]?>)
|
||||||
|
func handlePlayerItemStatusChange(playerItem:AVPlayerItem, change:NSKeyValueObservedChange<AVPlayerItem.Status>)
|
||||||
|
func handlePlaybackBufferKeyEmpty(playerItem:AVPlayerItem, change:NSKeyValueObservedChange<Bool>)
|
||||||
|
func handlePlaybackLikelyToKeepUp(playerItem:AVPlayerItem, change:NSKeyValueObservedChange<Bool>)
|
||||||
|
func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange<Float>)
|
||||||
|
func handleExternalPlaybackActiveChange(player: AVPlayer, change: NSKeyValueObservedChange<Bool>)
|
||||||
|
func handleViewControllerOverlayViewFrameChange(overlayView:UIView, change:NSKeyValueObservedChange<CGRect>)
|
||||||
|
}
|
||||||
|
|
||||||
|
class RCTPlayerObserver: NSObject {
|
||||||
|
|
||||||
|
var _handlers: RCTPlayerObserverHandler!
|
||||||
|
|
||||||
|
var player:AVPlayer? {
|
||||||
|
didSet {
|
||||||
|
if player == nil {
|
||||||
|
removePlayerObservers()
|
||||||
|
removePlayerTimeObserver()
|
||||||
|
} else {
|
||||||
|
addPlayerObservers()
|
||||||
|
addPlayerTimeObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var playerItem:AVPlayerItem? {
|
||||||
|
didSet {
|
||||||
|
if playerItem == nil {
|
||||||
|
removePlayerItemObservers()
|
||||||
|
} else {
|
||||||
|
addPlayerItemObservers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var playerViewController:AVPlayerViewController? {
|
||||||
|
didSet {
|
||||||
|
if playerViewController == nil {
|
||||||
|
removePlayerViewControllerObservers()
|
||||||
|
} else {
|
||||||
|
addPlayerViewControllerObservers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var playerLayer:AVPlayerLayer? {
|
||||||
|
didSet {
|
||||||
|
if playerLayer == nil {
|
||||||
|
removePlayerLayerObserver()
|
||||||
|
} else {
|
||||||
|
addPlayerLayerObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _progressUpdateInterval:TimeInterval = 250
|
||||||
|
private var _timeObserver:Any?
|
||||||
|
|
||||||
|
private var _playerRateChangeObserver:NSKeyValueObservation?
|
||||||
|
private var _playerExpernalPlaybackActiveObserver:NSKeyValueObservation?
|
||||||
|
private var _playerItemStatusObserver:NSKeyValueObservation?
|
||||||
|
private var _playerPlaybackBufferEmptyObserver:NSKeyValueObservation?
|
||||||
|
private var _playerPlaybackLikelyToKeepUpObserver:NSKeyValueObservation?
|
||||||
|
private var _playerTimedMetadataObserver:NSKeyValueObservation?
|
||||||
|
private var _playerViewControllerReadyForDisplayObserver:NSKeyValueObservation?
|
||||||
|
private var _playerLayerReadyForDisplayObserver:NSKeyValueObservation?
|
||||||
|
private var _playerViewControllerOverlayFrameObserver:NSKeyValueObservation?
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
NotificationCenter.default.removeObserver(_handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPlayerObservers() {
|
||||||
|
guard let player = player else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_playerRateChangeObserver = player.observe(\.rate, changeHandler: _handlers.handlePlaybackRateChange)
|
||||||
|
_playerExpernalPlaybackActiveObserver = player.observe(\.isExternalPlaybackActive, changeHandler: _handlers.handleExternalPlaybackActiveChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePlayerObservers() {
|
||||||
|
_playerRateChangeObserver?.invalidate()
|
||||||
|
_playerExpernalPlaybackActiveObserver?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPlayerItemObservers() {
|
||||||
|
guard let playerItem = playerItem else { return }
|
||||||
|
|
||||||
|
_playerItemStatusObserver = playerItem.observe(\.status, options: [.new, .old], changeHandler: _handlers.handlePlayerItemStatusChange)
|
||||||
|
_playerPlaybackBufferEmptyObserver = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new, .old], changeHandler: _handlers.handlePlaybackBufferKeyEmpty)
|
||||||
|
_playerPlaybackLikelyToKeepUpObserver = playerItem.observe(\.isPlaybackLikelyToKeepUp, options: [.new, .old], changeHandler: _handlers.handlePlaybackLikelyToKeepUp)
|
||||||
|
_playerTimedMetadataObserver = playerItem.observe(\.timedMetadata, options: [.new], changeHandler: _handlers.handleTimeMetadataChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePlayerItemObservers() {
|
||||||
|
_playerItemStatusObserver?.invalidate()
|
||||||
|
_playerPlaybackBufferEmptyObserver?.invalidate()
|
||||||
|
_playerPlaybackLikelyToKeepUpObserver?.invalidate()
|
||||||
|
_playerTimedMetadataObserver?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPlayerViewControllerObservers() {
|
||||||
|
guard let playerViewController = playerViewController else { return }
|
||||||
|
|
||||||
|
_playerViewControllerReadyForDisplayObserver = playerViewController.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay)
|
||||||
|
|
||||||
|
_playerViewControllerOverlayFrameObserver = playerViewController.contentOverlayView?.observe(\.frame, options: [.new, .old], changeHandler: _handlers.handleViewControllerOverlayViewFrameChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePlayerViewControllerObservers() {
|
||||||
|
_playerViewControllerReadyForDisplayObserver?.invalidate()
|
||||||
|
_playerViewControllerOverlayFrameObserver?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPlayerLayerObserver() {
|
||||||
|
_playerLayerReadyForDisplayObserver = playerLayer?.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePlayerLayerObserver() {
|
||||||
|
_playerLayerReadyForDisplayObserver?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPlayerTimeObserver() {
|
||||||
|
removePlayerTimeObserver()
|
||||||
|
let progressUpdateIntervalMS:Float64 = _progressUpdateInterval / 1000
|
||||||
|
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m
|
||||||
|
// of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html
|
||||||
|
_timeObserver = player?.addPeriodicTimeObserver(
|
||||||
|
forInterval: CMTimeMakeWithSeconds(progressUpdateIntervalMS, preferredTimescale: Int32(NSEC_PER_SEC)),
|
||||||
|
queue:nil,
|
||||||
|
using:_handlers.handleTimeUpdate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cancels the previously registered time observer. */
|
||||||
|
func removePlayerTimeObserver() {
|
||||||
|
if let timeObserver = _timeObserver {
|
||||||
|
player?.removeTimeObserver(timeObserver)
|
||||||
|
_timeObserver = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTimeObserverIfNotSet() {
|
||||||
|
if (_timeObserver == nil) {
|
||||||
|
addPlayerTimeObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceTimeObserverIfSet(_ newUpdateInterval:Float64? = nil) {
|
||||||
|
if let newUpdateInterval = newUpdateInterval {
|
||||||
|
_progressUpdateInterval = newUpdateInterval
|
||||||
|
}
|
||||||
|
if (_timeObserver != nil) {
|
||||||
|
addPlayerTimeObserver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachPlayerEventListeners() {
|
||||||
|
|
||||||
|
NotificationCenter.default.removeObserver(_handlers,
|
||||||
|
name:NSNotification.Name.AVPlayerItemDidPlayToEndTime,
|
||||||
|
object:player?.currentItem)
|
||||||
|
NotificationCenter.default.addObserver(_handlers,
|
||||||
|
selector:#selector(RCTPlayerObserverHandler.handlePlayerItemDidReachEnd(notification:)),
|
||||||
|
name:NSNotification.Name.AVPlayerItemDidPlayToEndTime,
|
||||||
|
object:player?.currentItem)
|
||||||
|
|
||||||
|
NotificationCenter.default.removeObserver(_handlers,
|
||||||
|
name:NSNotification.Name.AVPlayerItemPlaybackStalled,
|
||||||
|
object:nil)
|
||||||
|
NotificationCenter.default.addObserver(_handlers,
|
||||||
|
selector:#selector(RCTPlayerObserverHandler.handlePlaybackStalled(notification:)),
|
||||||
|
name:NSNotification.Name.AVPlayerItemPlaybackStalled,
|
||||||
|
object:nil)
|
||||||
|
NotificationCenter.default.removeObserver(_handlers,
|
||||||
|
name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime,
|
||||||
|
object:nil)
|
||||||
|
NotificationCenter.default.addObserver(_handlers,
|
||||||
|
selector:#selector(RCTPlayerObserverHandler.handleDidFailToFinishPlaying(notification:)),
|
||||||
|
name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime,
|
||||||
|
object:nil)
|
||||||
|
// Unused
|
||||||
|
// NotificationCenter.default.removeObserver(_handlers,
|
||||||
|
// name:NSNotification.Name.AVPlayerItemNewAccessLogEntry,
|
||||||
|
// object:nil)
|
||||||
|
// NotificationCenter.default.addObserver(_handlers,
|
||||||
|
// selector: #selector(RCTPlayerObserverHandler.handleAVPlayerAccess(notification:)),
|
||||||
|
// name:NSNotification.Name.AVPlayerItemNewAccessLogEntry,
|
||||||
|
// object:nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearPlayer() {
|
||||||
|
player = nil
|
||||||
|
playerItem = nil
|
||||||
|
NotificationCenter.default.removeObserver(_handlers)
|
||||||
|
}
|
||||||
|
}
|
160
ios/Video/Features/RCTPlayerOperations.swift
Normal file
160
ios/Video/Features/RCTPlayerOperations.swift
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import AVFoundation
|
||||||
|
import MediaAccessibility
|
||||||
|
|
||||||
|
let RCTVideoUnset = -1
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Collection of mutating functions
|
||||||
|
*/
|
||||||
|
enum RCTPlayerOperations {
|
||||||
|
|
||||||
|
static func setSideloadedText(player:AVPlayer?, textTracks:[TextTrack]?, criteria:SelectedTrackCriteria?) {
|
||||||
|
let type = criteria?.type
|
||||||
|
let textTracks:[TextTrack]! = textTracks ?? RCTVideoUtils.getTextTrackInfo(player)
|
||||||
|
|
||||||
|
// The first few tracks will be audio & video track
|
||||||
|
let firstTextIndex:Int = 0
|
||||||
|
for firstTextIndex in 0..<(player?.currentItem?.tracks.count ?? 0) {
|
||||||
|
if player?.currentItem?.tracks[firstTextIndex].assetTrack?.hasMediaCharacteristic(.legible) ?? false {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedTrackIndex:Int = RCTVideoUnset
|
||||||
|
|
||||||
|
if (type == "disabled") {
|
||||||
|
// Do nothing. We want to ensure option is nil
|
||||||
|
} else if (type == "language") {
|
||||||
|
let selectedValue = criteria?.value as? String
|
||||||
|
for i in 0..<textTracks.count {
|
||||||
|
let currentTextTrack = textTracks[i]
|
||||||
|
if (selectedValue == currentTextTrack.language) {
|
||||||
|
selectedTrackIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == "title") {
|
||||||
|
let selectedValue = criteria?.value as? String
|
||||||
|
for i in 0..<textTracks.count {
|
||||||
|
let currentTextTrack = textTracks[i]
|
||||||
|
if (selectedValue == currentTextTrack.title) {
|
||||||
|
selectedTrackIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == "index") {
|
||||||
|
if let value = criteria?.value, let index = value as? Int {
|
||||||
|
if textTracks.count > index {
|
||||||
|
selectedTrackIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in the situation that a selected text track is not available (eg. specifies a textTrack not available)
|
||||||
|
if (type != "disabled") && selectedTrackIndex == RCTVideoUnset {
|
||||||
|
let captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(.user) as! CFArray
|
||||||
|
let captionSettings = captioningMediaCharacteristics as? [AnyHashable]
|
||||||
|
if ((captionSettings?.contains(AVMediaCharacteristic.transcribesSpokenDialogForAccessibility)) != nil) {
|
||||||
|
selectedTrackIndex = 0 // If we can't find a match, use the first available track
|
||||||
|
let systemLanguage = NSLocale.preferredLanguages.first
|
||||||
|
for i in 0..<textTracks.count {
|
||||||
|
let currentTextTrack = textTracks[i]
|
||||||
|
if systemLanguage == currentTextTrack.language {
|
||||||
|
selectedTrackIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in firstTextIndex..<(player?.currentItem?.tracks.count ?? 0) {
|
||||||
|
var isEnabled = false
|
||||||
|
if selectedTrackIndex != RCTVideoUnset {
|
||||||
|
isEnabled = i == selectedTrackIndex + firstTextIndex
|
||||||
|
}
|
||||||
|
player?.currentItem?.tracks[i].isEnabled = isEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNUSED
|
||||||
|
static func setStreamingText(player:AVPlayer?, criteria:SelectedTrackCriteria?) {
|
||||||
|
let type = criteria?.type
|
||||||
|
let group:AVMediaSelectionGroup! = player?.currentItem?.asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristic.legible)
|
||||||
|
var mediaOption:AVMediaSelectionOption!
|
||||||
|
|
||||||
|
if (type == "disabled") {
|
||||||
|
// Do nothing. We want to ensure option is nil
|
||||||
|
} else if (type == "language") || (type == "title") {
|
||||||
|
let value = criteria?.value as? String
|
||||||
|
for i in 0..<group.options.count {
|
||||||
|
let currentOption:AVMediaSelectionOption! = group.options[i]
|
||||||
|
var optionValue:String!
|
||||||
|
if (type == "language") {
|
||||||
|
optionValue = currentOption.extendedLanguageTag
|
||||||
|
} else {
|
||||||
|
optionValue = currentOption.commonMetadata.map(\.value)[0] as! String
|
||||||
|
}
|
||||||
|
if (value == optionValue) {
|
||||||
|
mediaOption = currentOption
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//} else if ([type isEqualToString:@"default"]) {
|
||||||
|
// option = group.defaultOption; */
|
||||||
|
} else if (type == "index") {
|
||||||
|
if let value = criteria?.value, let index = value as? Int {
|
||||||
|
if group.options.count > index {
|
||||||
|
mediaOption = group.options[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // default. invalid type or "system"
|
||||||
|
player?.currentItem?.selectMediaOptionAutomatically(in: group)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a match isn't found, option will be nil and text tracks will be disabled
|
||||||
|
player?.currentItem?.select(mediaOption, in:group)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setMediaSelectionTrackForCharacteristic(player:AVPlayer?, characteristic:AVMediaCharacteristic, criteria:SelectedTrackCriteria?) {
|
||||||
|
let type = criteria?.type
|
||||||
|
let group:AVMediaSelectionGroup! = player?.currentItem?.asset.mediaSelectionGroup(forMediaCharacteristic: characteristic)
|
||||||
|
var mediaOption:AVMediaSelectionOption!
|
||||||
|
|
||||||
|
if (type == "disabled") {
|
||||||
|
// Do nothing. We want to ensure option is nil
|
||||||
|
} else if (type == "language") || (type == "title") {
|
||||||
|
let value = criteria?.value as? String
|
||||||
|
for i in 0..<group.options.count {
|
||||||
|
let currentOption:AVMediaSelectionOption! = group.options[i]
|
||||||
|
var optionValue:String!
|
||||||
|
if (type == "language") {
|
||||||
|
optionValue = currentOption.extendedLanguageTag
|
||||||
|
} else {
|
||||||
|
optionValue = currentOption.commonMetadata.map(\.value)[0] as? String
|
||||||
|
}
|
||||||
|
if (value == optionValue) {
|
||||||
|
mediaOption = currentOption
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//} else if ([type isEqualToString:@"default"]) {
|
||||||
|
// option = group.defaultOption; */
|
||||||
|
} else if type == "index" {
|
||||||
|
if let value = criteria?.value, let index = value as? Int {
|
||||||
|
if group.options.count > index {
|
||||||
|
mediaOption = group.options[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let group = group { // default. invalid type or "system"
|
||||||
|
player?.currentItem?.selectMediaOptionAutomatically(in: group)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let group = group {
|
||||||
|
// If a match isn't found, option will be nil and text tracks will be disabled
|
||||||
|
player?.currentItem?.select(mediaOption, in:group)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
258
ios/Video/Features/RCTResourceLoaderDelegate.swift
Normal file
258
ios/Video/Features/RCTResourceLoaderDelegate.swift
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate {
|
||||||
|
|
||||||
|
private var _loadingRequest:AVAssetResourceLoadingRequest?
|
||||||
|
private var _requestingCertificate:Bool = false
|
||||||
|
private var _requestingCertificateErrored:Bool = false
|
||||||
|
private var _drm: DRMParams?
|
||||||
|
private var _reactTag: NSNumber?
|
||||||
|
private var _onVideoError: RCTDirectEventBlock?
|
||||||
|
private var _onGetLicense: RCTDirectEventBlock?
|
||||||
|
|
||||||
|
|
||||||
|
init(
|
||||||
|
asset: AVURLAsset,
|
||||||
|
drm: DRMParams?,
|
||||||
|
onVideoError: RCTDirectEventBlock?,
|
||||||
|
onGetLicense: RCTDirectEventBlock?,
|
||||||
|
reactTag: NSNumber
|
||||||
|
) {
|
||||||
|
super.init()
|
||||||
|
let queue = DispatchQueue(label: "assetQueue")
|
||||||
|
asset.resourceLoader.setDelegate(self, queue: queue)
|
||||||
|
_reactTag = reactTag
|
||||||
|
_onVideoError = onVideoError
|
||||||
|
_onGetLicense = onGetLicense
|
||||||
|
_drm = drm
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
_loadingRequest?.finishLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, shouldWaitForRenewalOfRequestedResource renewalRequest:AVAssetResourceRenewalRequest) -> Bool {
|
||||||
|
return loadingRequestHandling(renewalRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest:AVAssetResourceLoadingRequest) -> Bool {
|
||||||
|
return loadingRequestHandling(loadingRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, didCancel loadingRequest:AVAssetResourceLoadingRequest) {
|
||||||
|
NSLog("didCancelLoadingRequest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func base64DataFromBase64String(base64String:String?) -> Data? {
|
||||||
|
if let base64String = base64String {
|
||||||
|
return Data(base64Encoded:base64String)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLicenseResult(_ license:String!) {
|
||||||
|
guard let respondData = self.base64DataFromBase64String(base64String: license),
|
||||||
|
let _loadingRequest = _loadingRequest else {
|
||||||
|
setLicenseResultError("No data from JS license response")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let dataRequest:AVAssetResourceLoadingDataRequest! = _loadingRequest.dataRequest
|
||||||
|
dataRequest.respond(with: respondData)
|
||||||
|
_loadingRequest.finishLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLicenseResultError(_ error:String!) {
|
||||||
|
if _loadingRequest != nil {
|
||||||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.fromJSPart(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func finishLoadingWithError(error:NSError!) -> Bool {
|
||||||
|
if let _loadingRequest = _loadingRequest, let error = error {
|
||||||
|
let licenseError:NSError! = error
|
||||||
|
_loadingRequest.finishLoading(with: licenseError)
|
||||||
|
|
||||||
|
_onVideoError?([
|
||||||
|
"error": [
|
||||||
|
"code": NSNumber(value: error.code),
|
||||||
|
"localizedDescription": error.localizedDescription == nil ? "" : error.localizedDescription,
|
||||||
|
"localizedFailureReason": ((error as NSError).localizedFailureReason == nil ? "" : (error as NSError).localizedFailureReason) ?? "",
|
||||||
|
"localizedRecoverySuggestion": ((error as NSError).localizedRecoverySuggestion == nil ? "" : (error as NSError).localizedRecoverySuggestion) ?? "",
|
||||||
|
"domain": (error as NSError).domain
|
||||||
|
],
|
||||||
|
"target": _reactTag
|
||||||
|
])
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadingRequestHandling(_ loadingRequest:AVAssetResourceLoadingRequest!) -> Bool {
|
||||||
|
if _requestingCertificate {
|
||||||
|
return true
|
||||||
|
} else if _requestingCertificateErrored {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_loadingRequest = loadingRequest
|
||||||
|
|
||||||
|
let url = loadingRequest.request.url
|
||||||
|
guard let _drm = _drm else {
|
||||||
|
return finishLoadingWithError(error: RCTVideoErrorHandler.noDRMData)
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentId:String!
|
||||||
|
let contentIdOverride:String! = _drm.contentId
|
||||||
|
if contentIdOverride != nil {
|
||||||
|
contentId = contentIdOverride
|
||||||
|
} else if (_onGetLicense != nil) {
|
||||||
|
contentId = url?.host
|
||||||
|
} else {
|
||||||
|
contentId = url?.absoluteString.replacingOccurrences(of: "skd://", with:"")
|
||||||
|
}
|
||||||
|
|
||||||
|
let drmType:String! = _drm.type
|
||||||
|
guard drmType == "fairplay" else {
|
||||||
|
return finishLoadingWithError(error: RCTVideoErrorHandler.noDRMData)
|
||||||
|
}
|
||||||
|
|
||||||
|
let certificateStringUrl:String! = _drm.certificateUrl
|
||||||
|
guard let certificateStringUrl = certificateStringUrl, let certificateURL = URL(string: certificateStringUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "") else {
|
||||||
|
return finishLoadingWithError(error: RCTVideoErrorHandler.noCertificateURL)
|
||||||
|
}
|
||||||
|
DispatchQueue.global().async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
var certificateData:Data?
|
||||||
|
if (_drm.base64Certificate != nil) {
|
||||||
|
certificateData = Data(base64Encoded: certificateData! as Data, options: .ignoreUnknownCharacters)
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
certificateData = try Data(contentsOf: certificateURL)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let certificateData = certificateData else {
|
||||||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noCertificateData)
|
||||||
|
self._requestingCertificateErrored = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentIdData:NSData!
|
||||||
|
if self._onGetLicense != nil {
|
||||||
|
contentIdData = contentId.data(using: .utf8) as NSData?
|
||||||
|
} else {
|
||||||
|
contentIdData = NSData(bytes: contentId.cString(using: String.Encoding.utf8), length:contentId.lengthOfBytes(using: String.Encoding.utf8))
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataRequest:AVAssetResourceLoadingDataRequest! = loadingRequest.dataRequest
|
||||||
|
guard dataRequest != nil else {
|
||||||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noCertificateData)
|
||||||
|
self._requestingCertificateErrored = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var spcError:NSError!
|
||||||
|
var spcData: Data?
|
||||||
|
do {
|
||||||
|
spcData = try loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData as Data, options: nil)
|
||||||
|
} catch let spcError {
|
||||||
|
print("SPC error")
|
||||||
|
}
|
||||||
|
// Request CKC to the server
|
||||||
|
var licenseServer:String! = _drm.licenseServer
|
||||||
|
if spcError != nil {
|
||||||
|
self.finishLoadingWithError(error: spcError)
|
||||||
|
self._requestingCertificateErrored = true
|
||||||
|
}
|
||||||
|
|
||||||
|
guard spcData != nil else {
|
||||||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noSPC)
|
||||||
|
self._requestingCertificateErrored = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// js client has a onGetLicense callback and will handle license fetching
|
||||||
|
if let _onGetLicense = self._onGetLicense {
|
||||||
|
let base64Encoded = spcData?.base64EncodedString(options: [])
|
||||||
|
self._requestingCertificate = true
|
||||||
|
if licenseServer == nil {
|
||||||
|
licenseServer = ""
|
||||||
|
}
|
||||||
|
_onGetLicense(["licenseUrl": licenseServer,
|
||||||
|
"contentId": contentId,
|
||||||
|
"spcBase64": base64Encoded,
|
||||||
|
"target": self._reactTag])
|
||||||
|
|
||||||
|
|
||||||
|
} else if licenseServer != nil {
|
||||||
|
self.fetchLicense(
|
||||||
|
licenseServer: licenseServer,
|
||||||
|
spcData: spcData,
|
||||||
|
contentId: contentId,
|
||||||
|
dataRequest: dataRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLicense(
|
||||||
|
licenseServer: String,
|
||||||
|
spcData: Data?,
|
||||||
|
contentId: String,
|
||||||
|
dataRequest: AVAssetResourceLoadingDataRequest!
|
||||||
|
) {
|
||||||
|
var request = URLRequest(url: URL(string: licenseServer)!)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
|
||||||
|
// HEADERS
|
||||||
|
if let headers = _drm?.headers {
|
||||||
|
for item in headers {
|
||||||
|
guard let key = item.key as? String, let value = item.value as? String else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
request.setValue(value, forHTTPHeaderField: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_onGetLicense != nil) {
|
||||||
|
request.httpBody = spcData
|
||||||
|
} else {
|
||||||
|
let spcEncoded = spcData?.base64EncodedString(options: [])
|
||||||
|
let spcUrlEncoded = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, spcEncoded as? CFString? as! CFString, nil, "?=&+" as CFString, CFStringBuiltInEncodings.UTF8.rawValue) as? String
|
||||||
|
let post = String(format:"spc=%@&%@", spcUrlEncoded as! CVarArg, contentId)
|
||||||
|
let postData = post.data(using: String.Encoding.utf8, allowLossyConversion:true)
|
||||||
|
request.httpBody = postData
|
||||||
|
}
|
||||||
|
|
||||||
|
let postDataTask = URLSession.shared.dataTask(with: request as URLRequest, completionHandler:{ [weak self] (data:Data!,response:URLResponse!,error:Error!) in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let httpResponse:HTTPURLResponse! = response as! HTTPURLResponse
|
||||||
|
guard error == nil else {
|
||||||
|
print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)")
|
||||||
|
self.finishLoadingWithError(error: error as NSError?)
|
||||||
|
self._requestingCertificateErrored = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard httpResponse.statusCode == 200 else {
|
||||||
|
print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)")
|
||||||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.licenseRequestNotOk(httpResponse.statusCode))
|
||||||
|
self._requestingCertificateErrored = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard data != nil else {
|
||||||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noDataFromLicenseRequest)
|
||||||
|
self._requestingCertificateErrored = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self._onGetLicense != nil) {
|
||||||
|
dataRequest.respond(with: data)
|
||||||
|
} else if let decodedData = Data(base64Encoded: data, options: []) {
|
||||||
|
dataRequest.respond(with: decodedData)
|
||||||
|
}
|
||||||
|
self._loadingRequest?.finishLoading()
|
||||||
|
})
|
||||||
|
postDataTask.resume()
|
||||||
|
}
|
||||||
|
}
|
83
ios/Video/Features/RCTVideoErrorHandling.swift
Normal file
83
ios/Video/Features/RCTVideoErrorHandling.swift
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
enum RCTVideoError : Int {
|
||||||
|
case fromJSPart
|
||||||
|
case licenseRequestNotOk
|
||||||
|
case noDataFromLicenseRequest
|
||||||
|
case noSPC
|
||||||
|
case noDataRequest
|
||||||
|
case noCertificateData
|
||||||
|
case noCertificateURL
|
||||||
|
case noFairplayDRM
|
||||||
|
case noDRMData
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RCTVideoErrorHandler {
|
||||||
|
|
||||||
|
static let noDRMData: NSError = NSError(
|
||||||
|
domain: "RCTVideo",
|
||||||
|
code: RCTVideoError.noDRMData.rawValue,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Error obtaining DRM license.",
|
||||||
|
NSLocalizedFailureReasonErrorKey: "No drm object found.",
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey: "Have you specified the 'drm' prop?"
|
||||||
|
])
|
||||||
|
|
||||||
|
static let noCertificateURL: NSError = NSError(
|
||||||
|
domain: "RCTVideo",
|
||||||
|
code: RCTVideoError.noCertificateURL.rawValue,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Error obtaining DRM License.",
|
||||||
|
NSLocalizedFailureReasonErrorKey: "No certificate URL has been found.",
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey: "Did you specified the prop certificateUrl?"
|
||||||
|
])
|
||||||
|
|
||||||
|
static let noCertificateData: NSError = NSError(
|
||||||
|
domain: "RCTVideo",
|
||||||
|
code: RCTVideoError.noCertificateData.rawValue,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Error obtaining DRM license.",
|
||||||
|
NSLocalizedFailureReasonErrorKey: "No certificate data obtained from the specificied url.",
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey: "Have you specified a valid 'certificateUrl'?"
|
||||||
|
])
|
||||||
|
|
||||||
|
static let noSPC:NSError! = NSError(
|
||||||
|
domain: "RCTVideo",
|
||||||
|
code: RCTVideoError.noSPC.rawValue,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Error obtaining license.",
|
||||||
|
NSLocalizedFailureReasonErrorKey: "No spc received.",
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey: "Check your DRM config."
|
||||||
|
])
|
||||||
|
|
||||||
|
static let noDataFromLicenseRequest:NSError! = NSError(
|
||||||
|
domain: "RCTVideo",
|
||||||
|
code: RCTVideoError.noDataFromLicenseRequest.rawValue,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Error obtaining DRM license.",
|
||||||
|
NSLocalizedFailureReasonErrorKey: "No data received from the license server.",
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey: "Is the licenseServer ok?."
|
||||||
|
])
|
||||||
|
|
||||||
|
static func licenseRequestNotOk(_ statusCode: Int) -> NSError {
|
||||||
|
return NSError(
|
||||||
|
domain: "RCTVideo",
|
||||||
|
code: RCTVideoError.licenseRequestNotOk.rawValue,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: "Error obtaining license.",
|
||||||
|
NSLocalizedFailureReasonErrorKey: String(
|
||||||
|
format:"License server responded with status code %li",
|
||||||
|
(statusCode)
|
||||||
|
),
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey: "Did you send the correct data to the license Server? Is the server ok?"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
static func fromJSPart(_ error: String) -> NSError {
|
||||||
|
return NSError(domain: "RCTVideo",
|
||||||
|
code: RCTVideoError.fromJSPart.rawValue,
|
||||||
|
userInfo: [
|
||||||
|
NSLocalizedDescriptionKey: error,
|
||||||
|
NSLocalizedFailureReasonErrorKey: error,
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey: error
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
75
ios/Video/Features/RCTVideoSave.swift
Normal file
75
ios/Video/Features/RCTVideoSave.swift
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
enum RCTVideoSave {
|
||||||
|
|
||||||
|
static func save(
|
||||||
|
options:NSDictionary!,
|
||||||
|
resolve: @escaping RCTPromiseResolveBlock,
|
||||||
|
reject:@escaping RCTPromiseRejectBlock,
|
||||||
|
|
||||||
|
playerItem: AVPlayerItem?
|
||||||
|
) {
|
||||||
|
let asset:AVAsset! = playerItem?.asset
|
||||||
|
|
||||||
|
guard asset != nil else {
|
||||||
|
reject("ERROR_ASSET_NIL", "Asset is nil", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let exportSession = AVAssetExportSession(asset: asset, presetName:AVAssetExportPresetHighestQuality) else {
|
||||||
|
reject("ERROR_COULD_NOT_CREATE_EXPORT_SESSION", "Could not create export session", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var path:String! = nil
|
||||||
|
path = RCTVideoSave.generatePathInDirectory(
|
||||||
|
directory: URL(fileURLWithPath: RCTVideoSave.cacheDirectoryPath() ?? "").appendingPathComponent("Videos").path,
|
||||||
|
withExtension: ".mp4")
|
||||||
|
let url:NSURL! = NSURL.fileURL(withPath: path) as NSURL
|
||||||
|
exportSession.outputFileType = AVFileType.mp4
|
||||||
|
exportSession.outputURL = url as URL?
|
||||||
|
exportSession.videoComposition = playerItem?.videoComposition
|
||||||
|
exportSession.shouldOptimizeForNetworkUse = true
|
||||||
|
exportSession.exportAsynchronously(completionHandler: {
|
||||||
|
|
||||||
|
switch (exportSession.status) {
|
||||||
|
case .failed:
|
||||||
|
reject("ERROR_COULD_NOT_EXPORT_VIDEO", "Could not export video", exportSession.error)
|
||||||
|
break
|
||||||
|
case .cancelled:
|
||||||
|
reject("ERROR_EXPORT_SESSION_CANCELLED", "Export session was cancelled", exportSession.error)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
resolve(["uri": url.absoluteString])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static func generatePathInDirectory(directory: String?, withExtension `extension`: String?) -> String? {
|
||||||
|
let fileName = UUID().uuidString + (`extension` ?? "")
|
||||||
|
RCTVideoSave.ensureDirExists(withPath: directory)
|
||||||
|
return URL(fileURLWithPath: directory ?? "").appendingPathComponent(fileName).path
|
||||||
|
}
|
||||||
|
|
||||||
|
static func cacheDirectoryPath() -> String? {
|
||||||
|
let array = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).map(\.path)
|
||||||
|
return array[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ensureDirExists(withPath path: String?) -> Bool {
|
||||||
|
var isDir: ObjCBool = false
|
||||||
|
var error: Error?
|
||||||
|
let exists = FileManager.default.fileExists(atPath: path ?? "", isDirectory: &isDir)
|
||||||
|
if !(exists && isDir.boolValue) {
|
||||||
|
do {
|
||||||
|
try FileManager.default.createDirectory(atPath: path ?? "", withIntermediateDirectories: true, attributes: nil)
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
if error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
143
ios/Video/Features/RCTVideoUtils.swift
Normal file
143
ios/Video/Features/RCTVideoUtils.swift
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Collection of pure functions
|
||||||
|
*/
|
||||||
|
enum RCTVideoUtils {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Calculates and returns the playable duration of the current player item using its loaded time ranges.
|
||||||
|
*
|
||||||
|
* \returns The playable duration of the current player item in seconds.
|
||||||
|
*/
|
||||||
|
static func calculatePlayableDuration(_ player:AVPlayer?) -> NSNumber {
|
||||||
|
guard let player = player,
|
||||||
|
let video:AVPlayerItem = player.currentItem,
|
||||||
|
video.status == AVPlayerItem.Status.readyToPlay else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectiveTimeRange:CMTimeRange?
|
||||||
|
for (_, value) in video.loadedTimeRanges.enumerated() {
|
||||||
|
let timeRange:CMTimeRange = value.timeRangeValue
|
||||||
|
if CMTimeRangeContainsTime(timeRange, time: video.currentTime()) {
|
||||||
|
effectiveTimeRange = timeRange
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let effectiveTimeRange = effectiveTimeRange {
|
||||||
|
let playableDuration:Float64 = CMTimeGetSeconds(CMTimeRangeGetEnd(effectiveTimeRange))
|
||||||
|
if playableDuration > 0 {
|
||||||
|
return playableDuration as NSNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static func urlFilePath(filepath:NSString!) -> NSURL! {
|
||||||
|
if filepath.contains("file://") {
|
||||||
|
return NSURL(string: filepath as String)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no file found, check if the file exists in the Document directory
|
||||||
|
let paths:[String]! = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
||||||
|
var relativeFilePath:String! = filepath.lastPathComponent
|
||||||
|
// the file may be multiple levels below the documents directory
|
||||||
|
let fileComponents:[String]! = filepath.components(separatedBy: "Documents/")
|
||||||
|
if fileComponents.count > 1 {
|
||||||
|
relativeFilePath = fileComponents[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
let path:String! = (paths.first! as NSString).appendingPathComponent(relativeFilePath)
|
||||||
|
if FileManager.default.fileExists(atPath: path) {
|
||||||
|
return NSURL.fileURL(withPath: path) as NSURL
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
static func playerItemSeekableTimeRange(_ player:AVPlayer?) -> CMTimeRange {
|
||||||
|
if let playerItem = player?.currentItem,
|
||||||
|
playerItem.status == .readyToPlay,
|
||||||
|
let firstItem = playerItem.seekableTimeRanges.first {
|
||||||
|
return firstItem.timeRangeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return (CMTimeRange.zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func playerItemDuration(_ player:AVPlayer?) -> CMTime {
|
||||||
|
if let playerItem = player?.currentItem,
|
||||||
|
playerItem.status == .readyToPlay {
|
||||||
|
return(playerItem.duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return(CMTime.invalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func calculateSeekableDuration(_ player:AVPlayer?) -> NSNumber {
|
||||||
|
let timeRange:CMTimeRange = RCTVideoUtils.playerItemSeekableTimeRange(player)
|
||||||
|
if CMTIME_IS_NUMERIC(timeRange.duration)
|
||||||
|
{
|
||||||
|
return NSNumber(value: CMTimeGetSeconds(timeRange.duration))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getAudioTrackInfo(_ player:AVPlayer?) -> [AnyObject]! {
|
||||||
|
guard let player = player else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let audioTracks:NSMutableArray! = NSMutableArray()
|
||||||
|
let group = player.currentItem?.asset.mediaSelectionGroup(forMediaCharacteristic: .audible)
|
||||||
|
for i in 0..<(group?.options.count ?? 0) {
|
||||||
|
let currentOption = group?.options[i]
|
||||||
|
var title = ""
|
||||||
|
let values = currentOption?.commonMetadata.map(\.value)
|
||||||
|
if (values?.count ?? 0) > 0, let value = values?[0] {
|
||||||
|
title = value as! String
|
||||||
|
}
|
||||||
|
let language:String! = currentOption?.extendedLanguageTag ?? ""
|
||||||
|
let audioTrack = [
|
||||||
|
"index": NSNumber(value: i),
|
||||||
|
"title": title,
|
||||||
|
"language": language
|
||||||
|
] as [String : Any]
|
||||||
|
audioTracks.add(audioTrack)
|
||||||
|
}
|
||||||
|
return audioTracks as [AnyObject]?
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getTextTrackInfo(_ player:AVPlayer?) -> [TextTrack]! {
|
||||||
|
guard let player = player else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// if streaming video, we extract the text tracks
|
||||||
|
var textTracks:[TextTrack] = []
|
||||||
|
let group = player.currentItem?.asset.mediaSelectionGroup(forMediaCharacteristic: .legible)
|
||||||
|
for i in 0..<(group?.options.count ?? 0) {
|
||||||
|
let currentOption = group?.options[i]
|
||||||
|
var title = ""
|
||||||
|
let values = currentOption?.commonMetadata.map(\.value)
|
||||||
|
if (values?.count ?? 0) > 0, let value = values?[0] {
|
||||||
|
title = value as! String
|
||||||
|
}
|
||||||
|
let language:String! = currentOption?.extendedLanguageTag ?? ""
|
||||||
|
let textTrack = TextTrack([
|
||||||
|
"index": NSNumber(value: i),
|
||||||
|
"title": title,
|
||||||
|
"language": language
|
||||||
|
])
|
||||||
|
textTracks.append(textTrack)
|
||||||
|
}
|
||||||
|
return textTracks
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNUSED
|
||||||
|
static func getCurrentTime(playerItem:AVPlayerItem?) -> Float {
|
||||||
|
return Float(CMTimeGetSeconds(playerItem?.currentTime() ?? .zero))
|
||||||
|
}
|
||||||
|
}
|
11
ios/Video/RCTSwiftLog/RCTSwiftLog.h
Normal file
11
ios/Video/RCTSwiftLog/RCTSwiftLog.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@interface RCTSwiftLog : NSObject
|
||||||
|
|
||||||
|
+ (void)error:(NSString * _Nonnull)message file:(NSString * _Nonnull)file line:(NSUInteger)line;
|
||||||
|
+ (void)warn:(NSString * _Nonnull)message file:(NSString * _Nonnull)file line:(NSUInteger)line;
|
||||||
|
+ (void)info:(NSString * _Nonnull)message file:(NSString * _Nonnull)file line:(NSUInteger)line;
|
||||||
|
+ (void)log:(NSString * _Nonnull)message file:(NSString * _Nonnull)file line:(NSUInteger)line;
|
||||||
|
+ (void)trace:(NSString * _Nonnull)message file:(NSString * _Nonnull)file line:(NSUInteger)line;
|
||||||
|
|
||||||
|
@end
|
32
ios/Video/RCTSwiftLog/RCTSwiftLog.m
Normal file
32
ios/Video/RCTSwiftLog/RCTSwiftLog.m
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#import <React/RCTLog.h>
|
||||||
|
|
||||||
|
#import "RCTSwiftLog.h"
|
||||||
|
|
||||||
|
@implementation RCTSwiftLog
|
||||||
|
|
||||||
|
+ (void)info:(NSString *)message file:(NSString *)file line:(NSUInteger)line
|
||||||
|
{
|
||||||
|
_RCTLogNativeInternal(RCTLogLevelInfo, file.UTF8String, (int)line, @"%@", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)warn:(NSString *)message file:(NSString *)file line:(NSUInteger)line
|
||||||
|
{
|
||||||
|
_RCTLogNativeInternal(RCTLogLevelWarning, file.UTF8String, (int)line, @"%@", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)error:(NSString *)message file:(NSString *)file line:(NSUInteger)line
|
||||||
|
{
|
||||||
|
_RCTLogNativeInternal(RCTLogLevelError, file.UTF8String, (int)line, @"%@", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)log:(NSString *)message file:(NSString *)file line:(NSUInteger)line
|
||||||
|
{
|
||||||
|
_RCTLogNativeInternal(RCTLogLevelInfo, file.UTF8String, (int)line, @"%@", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)trace:(NSString *)message file:(NSString *)file line:(NSUInteger)line
|
||||||
|
{
|
||||||
|
_RCTLogNativeInternal(RCTLogLevelTrace, file.UTF8String, (int)line, @"%@", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
53
ios/Video/RCTSwiftLog/RCTSwiftLog.swift
Normal file
53
ios/Video/RCTSwiftLog/RCTSwiftLog.swift
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// RCTLog.swift
|
||||||
|
// WebViewExample
|
||||||
|
//
|
||||||
|
// Created by Jimmy Dee on 4/5/17.
|
||||||
|
// Copyright © 2017 Branch Metrics. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Under at least some conditions, output from NSLog has been unavailable in the RNBranch module.
|
||||||
|
* Hence that module uses the RCTLog macros from <React/RCTLog.h>. The React logger is nicer than
|
||||||
|
* NSLog anyway, since it provides log levels with runtime filtering, file and line context and
|
||||||
|
* an identifier for the thread that logged the message.
|
||||||
|
*
|
||||||
|
* This wrapper lets you use functions with the same name in Swift. For example:
|
||||||
|
*
|
||||||
|
* RCTLogInfo("application launched")
|
||||||
|
*
|
||||||
|
* generates
|
||||||
|
*
|
||||||
|
* 2017-04-06 12:31:09.611 [info][tid:main][AppDelegate.swift:18] application launched
|
||||||
|
*
|
||||||
|
* This is currently part of this sample app. There may be some issues integrating it into an
|
||||||
|
* Objective-C library, either react-native-branch or react-native itself, but it may find its
|
||||||
|
* way into one or the other eventually. Feel free to reuse it as desired.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func RCTLogError(_ message: String, _ file: String=#file, _ line: UInt=#line) {
|
||||||
|
RCTSwiftLog.error(message, file: file, line: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RCTLogWarn(_ message: String, _ file: String=#file, _ line: UInt=#line) {
|
||||||
|
RCTSwiftLog.warn(message, file: file, line: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RCTLogInfo(_ message: String, _ file: String=#file, _ line: UInt=#line) {
|
||||||
|
RCTSwiftLog.info(message, file: file, line: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RCTLog(_ message: String, _ file: String=#file, _ line: UInt=#line) {
|
||||||
|
RCTSwiftLog.log(message, file: file, line: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RCTLogTrace(_ message: String, _ file: String=#file, _ line: UInt=#line) {
|
||||||
|
RCTSwiftLog.trace(message, file: file, line: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DebugLog(_ message: String) {
|
||||||
|
#if DEBUG
|
||||||
|
print(message)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
7
ios/Video/RCTVideo-Bridging-Header.h
Normal file
7
ios/Video/RCTVideo-Bridging-Header.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#import <React/RCTViewManager.h>
|
||||||
|
#import "RCTSwiftLog.h"
|
||||||
|
|
||||||
|
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||||
|
#import "RCTVideoCache.h"
|
||||||
|
#endif
|
||||||
|
|
@ -1,67 +0,0 @@
|
|||||||
#import <AVFoundation/AVFoundation.h>
|
|
||||||
#import "AVKit/AVKit.h"
|
|
||||||
#import "UIView+FindUIViewController.h"
|
|
||||||
#import "RCTVideoPlayerViewController.h"
|
|
||||||
#import "RCTVideoPlayerViewControllerDelegate.h"
|
|
||||||
#import <React/RCTComponent.h>
|
|
||||||
#import <React/RCTBridgeModule.h>
|
|
||||||
|
|
||||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
|
||||||
#import <react-native-video/RCTVideoCache.h>
|
|
||||||
#import <DVAssetLoaderDelegate/DVURLAsset.h>
|
|
||||||
#import <DVAssetLoaderDelegate/DVAssetLoaderDelegate.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@class RCTEventDispatcher;
|
|
||||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate, AVAssetResourceLoaderDelegate>
|
|
||||||
#elif TARGET_OS_TV
|
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVAssetResourceLoaderDelegate>
|
|
||||||
#else
|
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate, AVAssetResourceLoaderDelegate>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoad;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoBuffer;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoError;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoProgress;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onBandwidthUpdate;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoSeek;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoEnd;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onTimedMetadata;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoAudioBecomingNoisy;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillPresent;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidPresent;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillDismiss;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidDismiss;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onReadyForDisplay;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackStalled;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackResume;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackRateChange;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onGetLicense;
|
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, RCTVideoError) {
|
|
||||||
RCTVideoErrorFromJSPart,
|
|
||||||
RCTVideoErrorLicenseRequestNotOk,
|
|
||||||
RCTVideoErrorNoDataFromLicenseRequest,
|
|
||||||
RCTVideoErrorNoSPC,
|
|
||||||
RCTVideoErrorNoDataRequest,
|
|
||||||
RCTVideoErrorNoCertificateData,
|
|
||||||
RCTVideoErrorNoCertificateURL,
|
|
||||||
RCTVideoErrorNoFairplayDRM,
|
|
||||||
RCTVideoErrorNoDRMData
|
|
||||||
};
|
|
||||||
|
|
||||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
|
||||||
|
|
||||||
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem;
|
|
||||||
|
|
||||||
- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
|
|
||||||
- (void)setLicenseResult:(NSString * )license;
|
|
||||||
- (BOOL)setLicenseResultError:(NSString * )error;
|
|
||||||
|
|
||||||
@end
|
|
2013
ios/Video/RCTVideo.m
2013
ios/Video/RCTVideo.m
File diff suppressed because it is too large
Load Diff
1147
ios/Video/RCTVideo.swift
Normal file
1147
ios/Video/RCTVideo.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +0,0 @@
|
|||||||
#import <React/RCTViewManager.h>
|
|
||||||
#import <React/RCTBridgeModule.h>
|
|
||||||
|
|
||||||
@interface RCTVideoManager : RCTViewManager <RCTBridgeModule>
|
|
||||||
|
|
||||||
@end
|
|
@ -1,22 +1,7 @@
|
|||||||
#import "RCTVideoManager.h"
|
|
||||||
#import "RCTVideo.h"
|
|
||||||
#import <React/RCTBridge.h>
|
#import <React/RCTBridge.h>
|
||||||
#import <React/RCTUIManager.h>
|
#import "React/RCTViewManager.h"
|
||||||
#import <AVFoundation/AVFoundation.h>
|
|
||||||
|
|
||||||
@implementation RCTVideoManager
|
@interface RCT_EXTERN_MODULE(RCTVideoManager, RCTViewManager)
|
||||||
|
|
||||||
RCT_EXPORT_MODULE();
|
|
||||||
|
|
||||||
- (UIView *)view
|
|
||||||
{
|
|
||||||
return [[RCTVideo alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (dispatch_queue_t)methodQueue
|
|
||||||
{
|
|
||||||
return self.bridge.uiManager.methodQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);
|
||||||
@ -49,6 +34,7 @@ RCT_EXPORT_VIEW_PROPERTY(filter, NSString);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
|
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
|
||||||
|
|
||||||
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
|
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);
|
||||||
@ -70,64 +56,18 @@ RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock);
|
||||||
RCT_REMAP_METHOD(save,
|
|
||||||
options:(NSDictionary *)options
|
|
||||||
reactTag:(nonnull NSNumber *)reactTag
|
|
||||||
resolver:(RCTPromiseResolveBlock)resolve
|
|
||||||
rejecter:(RCTPromiseRejectBlock)reject)
|
|
||||||
{
|
|
||||||
[self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTVideo *> *viewRegistry) {
|
|
||||||
RCTVideo *view = viewRegistry[reactTag];
|
|
||||||
if (![view isKindOfClass:[RCTVideo class]]) {
|
|
||||||
RCTLogError(@"Invalid view returned from registry, expecting RCTVideo, got: %@", view);
|
|
||||||
} else {
|
|
||||||
[view save:options resolve:resolve reject:reject];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
RCT_REMAP_METHOD(setLicenseResult,
|
|
||||||
license:(NSString *)license
|
|
||||||
reactTag:(nonnull NSNumber *)reactTag)
|
|
||||||
{
|
|
||||||
[self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTVideo *> *viewRegistry) {
|
|
||||||
RCTVideo *view = viewRegistry[reactTag];
|
|
||||||
if (![view isKindOfClass:[RCTVideo class]]) {
|
|
||||||
RCTLogError(@"Invalid view returned from registry, expecting RCTVideo, got: %@", view);
|
|
||||||
} else {
|
|
||||||
[view setLicenseResult:license];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
|
|
||||||
RCT_REMAP_METHOD(setLicenseResultError,
|
|
||||||
error:(NSString *)error
|
|
||||||
reactTag:(nonnull NSNumber *)reactTag)
|
|
||||||
{
|
|
||||||
[self.bridge.uiManager prependUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTVideo *> *viewRegistry) {
|
|
||||||
RCTVideo *view = viewRegistry[reactTag];
|
|
||||||
if (![view isKindOfClass:[RCTVideo class]]) {
|
|
||||||
RCTLogError(@"Invalid view returned from registry, expecting RCTVideo, got: %@", view);
|
|
||||||
} else {
|
|
||||||
[view setLicenseResultError:error];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
||||||
|
|
||||||
- (NSDictionary *)constantsToExport
|
RCT_EXTERN_METHOD(save:(NSDictionary *)options
|
||||||
{
|
reactTag:(nonnull NSNumber *)reactTag
|
||||||
return @{
|
resolver:(RCTPromiseResolveBlock)resolve
|
||||||
@"ScaleNone": AVLayerVideoGravityResizeAspect,
|
rejecter:(RCTPromiseRejectBlock)reject)
|
||||||
@"ScaleToFill": AVLayerVideoGravityResize,
|
|
||||||
@"ScaleAspectFit": AVLayerVideoGravityResizeAspect,
|
|
||||||
@"ScaleAspectFill": AVLayerVideoGravityResizeAspectFill
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (BOOL)requiresMainQueueSetup
|
RCT_EXTERN_METHOD(setLicenseResult:(NSString *)license
|
||||||
{
|
reactTag:(nonnull NSNumber *)reactTag)
|
||||||
return YES;
|
|
||||||
}
|
RCT_EXTERN_METHOD(setLicenseResultError(NSString *)error
|
||||||
|
reactTag:(nonnull NSNumber *)reactTag)
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
63
ios/Video/RCTVideoManager.swift
Normal file
63
ios/Video/RCTVideoManager.swift
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import AVFoundation
|
||||||
|
import React
|
||||||
|
|
||||||
|
@objc(RCTVideoManager)
|
||||||
|
class RCTVideoManager: RCTViewManager {
|
||||||
|
|
||||||
|
override func view() -> UIView {
|
||||||
|
return RCTVideo(eventDispatcher: bridge.eventDispatcher())
|
||||||
|
}
|
||||||
|
|
||||||
|
func methodQueue() -> DispatchQueue {
|
||||||
|
return bridge.uiManager.methodQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(save:reactTag:resolver:rejecter:)
|
||||||
|
func save(options: NSDictionary, reactTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
|
||||||
|
bridge.uiManager.prependUIBlock({_ , viewRegistry in
|
||||||
|
let view = viewRegistry?[reactTag]
|
||||||
|
if !(view is RCTVideo) {
|
||||||
|
RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view))
|
||||||
|
} else if let view = view as? RCTVideo {
|
||||||
|
view.save(options: options, resolve: resolve, reject: reject)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(setLicenseResult:reactTag:)
|
||||||
|
func setLicenseResult(license: NSString, reactTag: NSNumber) -> Void {
|
||||||
|
bridge.uiManager.prependUIBlock({_ , viewRegistry in
|
||||||
|
let view = viewRegistry?[reactTag]
|
||||||
|
if !(view is RCTVideo) {
|
||||||
|
RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view))
|
||||||
|
} else if let view = view as? RCTVideo {
|
||||||
|
view.setLicenseResult(license as String)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(setLicenseResultError:reactTag:)
|
||||||
|
func setLicenseResultError(error: NSString, reactTag: NSNumber) -> Void {
|
||||||
|
bridge.uiManager.prependUIBlock({_ , viewRegistry in
|
||||||
|
let view = viewRegistry?[reactTag]
|
||||||
|
if !(view is RCTVideo) {
|
||||||
|
RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view))
|
||||||
|
} else if let view = view as? RCTVideo {
|
||||||
|
view.setLicenseResultError(error as String)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override func constantsToExport() -> [AnyHashable : Any]? {
|
||||||
|
return [
|
||||||
|
"ScaleNone": AVLayerVideoGravity.resizeAspect,
|
||||||
|
"ScaleToFill": AVLayerVideoGravity.resize,
|
||||||
|
"ScaleAspectFit": AVLayerVideoGravity.resizeAspect,
|
||||||
|
"ScaleAspectFill": AVLayerVideoGravity.resizeAspectFill
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
override class func requiresMainQueueSetup() -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
//
|
|
||||||
// RCTVideoPlayerViewController.h
|
|
||||||
// RCTVideo
|
|
||||||
//
|
|
||||||
// Created by Stanisław Chmiela on 31.03.2016.
|
|
||||||
// Copyright © 2016 Facebook. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <AVKit/AVKit.h>
|
|
||||||
#import "RCTVideo.h"
|
|
||||||
#import "RCTVideoPlayerViewControllerDelegate.h"
|
|
||||||
|
|
||||||
@interface RCTVideoPlayerViewController : AVPlayerViewController
|
|
||||||
@property (nonatomic, weak) id<RCTVideoPlayerViewControllerDelegate> rctDelegate;
|
|
||||||
|
|
||||||
// Optional paramters
|
|
||||||
@property (nonatomic, weak) NSString* preferredOrientation;
|
|
||||||
@property (nonatomic) BOOL autorotate;
|
|
||||||
|
|
||||||
@end
|
|
@ -1,43 +0,0 @@
|
|||||||
#import "RCTVideoPlayerViewController.h"
|
|
||||||
|
|
||||||
@interface RCTVideoPlayerViewController ()
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation RCTVideoPlayerViewController
|
|
||||||
|
|
||||||
- (BOOL)shouldAutorotate {
|
|
||||||
|
|
||||||
if (self.autorotate || self.preferredOrientation.lowercaseString == nil || [self.preferredOrientation.lowercaseString isEqualToString:@"all"])
|
|
||||||
return YES;
|
|
||||||
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewDidDisappear:(BOOL)animated
|
|
||||||
{
|
|
||||||
[super viewDidDisappear:animated];
|
|
||||||
[_rctDelegate videoPlayerViewControllerWillDismiss:self];
|
|
||||||
[_rctDelegate videoPlayerViewControllerDidDismiss:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
|
||||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
|
|
||||||
return UIInterfaceOrientationMaskAll;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
|
|
||||||
if ([self.preferredOrientation.lowercaseString isEqualToString:@"landscape"]) {
|
|
||||||
return UIInterfaceOrientationLandscapeRight;
|
|
||||||
}
|
|
||||||
else if ([self.preferredOrientation.lowercaseString isEqualToString:@"portrait"]) {
|
|
||||||
return UIInterfaceOrientationPortrait;
|
|
||||||
}
|
|
||||||
else { // default case
|
|
||||||
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
|
|
||||||
return orientation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@end
|
|
44
ios/Video/RCTVideoPlayerViewController.swift
Normal file
44
ios/Video/RCTVideoPlayerViewController.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import AVKit
|
||||||
|
|
||||||
|
class RCTVideoPlayerViewController: AVPlayerViewController {
|
||||||
|
|
||||||
|
var rctDelegate:RCTVideoPlayerViewControllerDelegate!
|
||||||
|
|
||||||
|
// Optional paramters
|
||||||
|
var preferredOrientation:String?
|
||||||
|
var autorotate:Bool?
|
||||||
|
|
||||||
|
func shouldAutorotate() -> Bool {
|
||||||
|
|
||||||
|
if autorotate! || preferredOrientation == nil || (preferredOrientation!.lowercased() == "all") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
rctDelegate.videoPlayerViewControllerWillDismiss(playerViewController: self)
|
||||||
|
rctDelegate.videoPlayerViewControllerDidDismiss(playerViewController: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !TARGET_OS_TV
|
||||||
|
|
||||||
|
func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
|
||||||
|
return .all
|
||||||
|
}
|
||||||
|
|
||||||
|
func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
|
||||||
|
if preferredOrientation?.lowercased() == "landscape" {
|
||||||
|
return .landscapeRight
|
||||||
|
} else if preferredOrientation?.lowercased() == "portrait" {
|
||||||
|
return .portrait
|
||||||
|
} else {
|
||||||
|
// default case
|
||||||
|
let orientation = UIApplication.shared.statusBarOrientation
|
||||||
|
return orientation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import "AVKit/AVKit.h"
|
|
||||||
|
|
||||||
@protocol RCTVideoPlayerViewControllerDelegate <NSObject>
|
|
||||||
- (void)videoPlayerViewControllerWillDismiss:(AVPlayerViewController *)playerViewController;
|
|
||||||
- (void)videoPlayerViewControllerDidDismiss:(AVPlayerViewController *)playerViewController;
|
|
||||||
@end
|
|
7
ios/Video/RCTVideoPlayerViewControllerDelegate.swift
Normal file
7
ios/Video/RCTVideoPlayerViewControllerDelegate.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
import AVKit
|
||||||
|
|
||||||
|
protocol RCTVideoPlayerViewControllerDelegate : NSObject {
|
||||||
|
func videoPlayerViewControllerWillDismiss(playerViewController:AVPlayerViewController)
|
||||||
|
func videoPlayerViewControllerDidDismiss(playerViewController:AVPlayerViewController)
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
//
|
|
||||||
// UIView+FindUIViewController.h
|
|
||||||
// RCTVideo
|
|
||||||
//
|
|
||||||
// Created by Stanisław Chmiela on 31.03.2016.
|
|
||||||
// Copyright © 2016 Facebook. All rights reserved.
|
|
||||||
//
|
|
||||||
// Source: http://stackoverflow.com/a/3732812/1123156
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
@interface UIView (FindUIViewController)
|
|
||||||
- (UIViewController *) firstAvailableUIViewController;
|
|
||||||
- (id) traverseResponderChainForUIViewController;
|
|
||||||
@end
|
|
@ -1,21 +0,0 @@
|
|||||||
// Source: http://stackoverflow.com/a/3732812/1123156
|
|
||||||
|
|
||||||
#import "UIView+FindUIViewController.h"
|
|
||||||
|
|
||||||
@implementation UIView (FindUIViewController)
|
|
||||||
- (UIViewController *) firstAvailableUIViewController {
|
|
||||||
// convenience function for casting and to "mask" the recursive function
|
|
||||||
return (UIViewController *)[self traverseResponderChainForUIViewController];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id) traverseResponderChainForUIViewController {
|
|
||||||
id nextResponder = [self nextResponder];
|
|
||||||
if ([nextResponder isKindOfClass:[UIViewController class]]) {
|
|
||||||
return nextResponder;
|
|
||||||
} else if ([nextResponder isKindOfClass:[UIView class]]) {
|
|
||||||
return [nextResponder traverseResponderChainForUIViewController];
|
|
||||||
} else {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@end
|
|
18
ios/Video/UIView+FindUIViewController.swift
Normal file
18
ios/Video/UIView+FindUIViewController.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Source: http://stackoverflow.com/a/3732812/1123156
|
||||||
|
|
||||||
|
extension UIView {
|
||||||
|
func firstAvailableUIViewController() -> UIViewController? {
|
||||||
|
// convenience function for casting and to "mask" the recursive function
|
||||||
|
return traverseResponderChainForUIViewController()
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverseResponderChainForUIViewController() -> UIViewController? {
|
||||||
|
if let nextUIViewController = next as? UIViewController {
|
||||||
|
return nextUIViewController
|
||||||
|
} else if let nextUIView = next as? UIView {
|
||||||
|
return nextUIView.traverseResponderChainForUIViewController()
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
ios/VideoCaching/RCTVideoCachingHandler.swift
Normal file
81
ios/VideoCaching/RCTVideoCachingHandler.swift
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
import DVAssetLoaderDelegate
|
||||||
|
|
||||||
|
class RCTVideoCachingHandler: NSObject, DVAssetLoaderDelegatesDelegate {
|
||||||
|
|
||||||
|
private var _videoCache:RCTVideoCache! = RCTVideoCache.sharedInstance()
|
||||||
|
private var _playerItemPrepareText: (AVAsset?, NSDictionary?, (AVPlayerItem?)->Void) -> Void
|
||||||
|
|
||||||
|
init(_ playerItemPrepareText: @escaping (AVAsset?, NSDictionary?, (AVPlayerItem?)->Void) -> Void) {
|
||||||
|
_playerItemPrepareText = playerItemPrepareText
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerItemForSourceUsingCache(shouldCache:Bool, textTracks:[AnyObject]?, uri:String, assetOptions:NSMutableDictionary, handler:@escaping (AVPlayerItem?)->Void) -> Bool {
|
||||||
|
if shouldCache && ((textTracks == nil) || (textTracks!.count == 0)) {
|
||||||
|
/* The DVURLAsset created by cache doesn't have a tracksWithMediaType property, so trying
|
||||||
|
* to bring in the text track code will crash. I suspect this is because the asset hasn't fully loaded.
|
||||||
|
* Until this is fixed, we need to bypass caching when text tracks are specified.
|
||||||
|
*/
|
||||||
|
DebugLog("Caching is not supported for uri '\(uri)' because text tracks are not compatible with the cache. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md")
|
||||||
|
playerItemForSourceUsingCache(uri: uri, assetOptions:assetOptions, withCallback:handler)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func playerItemForSourceUsingCache(uri:String!, assetOptions options:NSDictionary!, withCallback handler: @escaping (AVPlayerItem?)->Void) {
|
||||||
|
let url = URL(string: uri)
|
||||||
|
_videoCache.getItemForUri(uri, withCallback:{ [weak self] (videoCacheStatus:RCTVideoCacheStatus,cachedAsset:AVAsset?) in
|
||||||
|
guard let self = self else { return }
|
||||||
|
switch (videoCacheStatus) {
|
||||||
|
case .missingFileExtension:
|
||||||
|
DebugLog("Could not generate cache key for uri '\(uri)'. It is currently not supported to cache urls that do not include a file extension. The video file will not be cached. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md")
|
||||||
|
let asset:AVURLAsset! = AVURLAsset(url: url!, options:options as! [String : Any])
|
||||||
|
self._playerItemPrepareText(asset, options, handler)
|
||||||
|
return
|
||||||
|
|
||||||
|
case .unsupportedFileExtension:
|
||||||
|
DebugLog("Could not generate cache key for uri '\(uri)'. The file extension of that uri is currently not supported. The video file will not be cached. Checkout https://github.com/react-native-community/react-native-video/blob/master/docs/caching.md")
|
||||||
|
let asset:AVURLAsset! = AVURLAsset(url: url!, options:options as! [String : Any])
|
||||||
|
self._playerItemPrepareText(asset, options, handler)
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
if let cachedAsset = cachedAsset {
|
||||||
|
DebugLog("Playing back uri '\(uri)' from cache")
|
||||||
|
// See note in playerItemForSource about not being able to support text tracks & caching
|
||||||
|
handler(AVPlayerItem(asset: cachedAsset))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let asset:DVURLAsset! = DVURLAsset(url:url, options:options as! [String : Any], networkTimeout:10000)
|
||||||
|
asset.loaderDelegate = self
|
||||||
|
|
||||||
|
/* More granular code to have control over the DVURLAsset
|
||||||
|
let resourceLoaderDelegate = DVAssetLoaderDelegate(url: url)
|
||||||
|
resourceLoaderDelegate.delegate = self
|
||||||
|
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: false)
|
||||||
|
components?.scheme = DVAssetLoaderDelegate.scheme()
|
||||||
|
var asset: AVURLAsset? = nil
|
||||||
|
if let url = components?.url {
|
||||||
|
asset = AVURLAsset(url: url, options: options)
|
||||||
|
}
|
||||||
|
asset?.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)
|
||||||
|
*/
|
||||||
|
|
||||||
|
handler(AVPlayerItem(asset: asset))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - DVAssetLoaderDelegate
|
||||||
|
|
||||||
|
func dvAssetLoaderDelegate(loaderDelegate:DVAssetLoaderDelegate!, didLoadData data:NSData!, forURL url:NSURL!) {
|
||||||
|
_videoCache.storeItem(data as Data?, forUri:url.absoluteString, withCallback:{ (success:Bool) in
|
||||||
|
DebugLog("Cache data stored successfully 🎉")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,8 +16,7 @@ Pod::Spec.new do |s|
|
|||||||
s.tvos.deployment_target = "9.0"
|
s.tvos.deployment_target = "9.0"
|
||||||
|
|
||||||
s.subspec "Video" do |ss|
|
s.subspec "Video" do |ss|
|
||||||
ss.source_files = "ios/Video/*.{h,m}"
|
ss.source_files = "ios/Video/**/*.{h,m,swift}"
|
||||||
s.static_framework = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
s.subspec "VideoCaching" do |ss|
|
s.subspec "VideoCaching" do |ss|
|
||||||
@ -25,14 +24,15 @@ Pod::Spec.new do |s|
|
|||||||
ss.dependency "SPTPersistentCache", "~> 1.1.0"
|
ss.dependency "SPTPersistentCache", "~> 1.1.0"
|
||||||
ss.dependency "DVAssetLoaderDelegate", "~> 0.3.1"
|
ss.dependency "DVAssetLoaderDelegate", "~> 0.3.1"
|
||||||
|
|
||||||
ss.source_files = "ios/VideoCaching/**/*.{h,m}"
|
ss.source_files = "ios/VideoCaching/**/*.{h,m,swift}"
|
||||||
s.static_framework = true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
s.dependency "React-Core"
|
s.dependency "React-Core"
|
||||||
|
|
||||||
s.default_subspec = "Video"
|
s.default_subspec = "Video"
|
||||||
|
|
||||||
|
s.static_framework = true
|
||||||
|
|
||||||
s.xcconfig = {
|
s.xcconfig = {
|
||||||
'OTHER_LDFLAGS': '-ObjC',
|
'OTHER_LDFLAGS': '-ObjC',
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user