├── .DS_Store ├── JPPictureInPictureDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── zhoujianping.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── zhoujianping.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── JPPictureInPictureDemo ├── .DS_Store ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── com_left_white_icon.imageset │ │ ├── Contents.json │ │ ├── com_left_white_icon@2x.png │ │ └── com_left_white_icon@3x.png │ ├── find_play_icon.imageset │ │ ├── Contents.json │ │ ├── find_play_icon@2x.png │ │ └── find_play_icon@3x.png │ └── find_suspend_icon.imageset │ │ ├── Contents.json │ │ ├── find_suspend_icon@2x.png │ │ └── find_suspend_icon@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Classse │ ├── .DS_Store │ ├── Constont.swift │ ├── FunButton.swift │ └── Player │ │ ├── PlayerControlView.swift │ │ ├── PlayerView.swift │ │ └── PlayerViewController.swift ├── Info.plist ├── Resource │ ├── .DS_Store │ ├── iphone-11-pro.mp4 │ ├── iphone-11-pro_0.jpg │ ├── iphone-11.mp4 │ ├── iphone-11_0.jpg │ ├── iphone-11_1.jpg │ └── iphone-11_2.jpg ├── SceneDelegate.swift ├── ViewController.swift ├── iPhone11ProViewController.swift └── iPhone11ViewController.swift └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/.DS_Store -------------------------------------------------------------------------------- /JPPictureInPictureDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1A5BB67D24A345EA00368BEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5BB67C24A345EA00368BEF /* AppDelegate.swift */; }; 11 | 1A5BB67F24A345EA00368BEF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5BB67E24A345EA00368BEF /* SceneDelegate.swift */; }; 12 | 1A5BB68124A345EA00368BEF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5BB68024A345EA00368BEF /* ViewController.swift */; }; 13 | 1A5BB68424A345EA00368BEF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A5BB68224A345EA00368BEF /* Main.storyboard */; }; 14 | 1A5BB68624A345EA00368BEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A5BB68524A345EA00368BEF /* Assets.xcassets */; }; 15 | 1A5BB68924A345EA00368BEF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A5BB68724A345EA00368BEF /* LaunchScreen.storyboard */; }; 16 | 1A8B41FA25B85FA300982216 /* Constont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8B41F825B85FA300982216 /* Constont.swift */; }; 17 | 1A8B41FB25B85FA300982216 /* FunButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8B41F925B85FA300982216 /* FunButton.swift */; }; 18 | 1ADB1A4824A8D8E100D4509F /* iPhone11ProViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADB1A4724A8D8E100D4509F /* iPhone11ProViewController.swift */; }; 19 | 1ADB1A5624A8DB8B00D4509F /* iPhone11ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5524A8DB8B00D4509F /* iPhone11ViewController.swift */; }; 20 | 1ADB1A5824A8DD5200D4509F /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5724A8DD5200D4509F /* README.md */; }; 21 | 1ADB1A6724A99D3200D4509F /* iphone-11-pro.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5A24A99D3200D4509F /* iphone-11-pro.mp4 */; }; 22 | 1ADB1A6824A99D3200D4509F /* iphone-11-pro_0.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5B24A99D3200D4509F /* iphone-11-pro_0.jpg */; }; 23 | 1ADB1A6924A99D3200D4509F /* iphone-11_1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5C24A99D3200D4509F /* iphone-11_1.jpg */; }; 24 | 1ADB1A6A24A99D3200D4509F /* iphone-11_0.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5D24A99D3200D4509F /* iphone-11_0.jpg */; }; 25 | 1ADB1A6B24A99D3200D4509F /* iphone-11_2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5E24A99D3200D4509F /* iphone-11_2.jpg */; }; 26 | 1ADB1A6C24A99D3200D4509F /* iphone-11.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 1ADB1A5F24A99D3200D4509F /* iphone-11.mp4 */; }; 27 | 1ADB1A7B24A99DA500D4509F /* PlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADB1A7624A99DA500D4509F /* PlayerControlView.swift */; }; 28 | 1ADB1A7C24A99DA500D4509F /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADB1A7724A99DA500D4509F /* PlayerView.swift */; }; 29 | 1ADB1A7D24A99DA500D4509F /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ADB1A7824A99DA500D4509F /* PlayerViewController.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1A5BB67924A345EA00368BEF /* JPPictureInPictureDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JPPictureInPictureDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 1A5BB67C24A345EA00368BEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | 1A5BB67E24A345EA00368BEF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 36 | 1A5BB68024A345EA00368BEF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 37 | 1A5BB68324A345EA00368BEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | 1A5BB68524A345EA00368BEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | 1A5BB68824A345EA00368BEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 40 | 1A5BB68A24A345EA00368BEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 1A8B41F825B85FA300982216 /* Constont.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constont.swift; sourceTree = ""; }; 42 | 1A8B41F925B85FA300982216 /* FunButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunButton.swift; sourceTree = ""; }; 43 | 1ADB1A4724A8D8E100D4509F /* iPhone11ProViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPhone11ProViewController.swift; sourceTree = ""; }; 44 | 1ADB1A5524A8DB8B00D4509F /* iPhone11ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPhone11ViewController.swift; sourceTree = ""; }; 45 | 1ADB1A5724A8DD5200D4509F /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 46 | 1ADB1A5A24A99D3200D4509F /* iphone-11-pro.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iphone-11-pro.mp4"; sourceTree = ""; }; 47 | 1ADB1A5B24A99D3200D4509F /* iphone-11-pro_0.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "iphone-11-pro_0.jpg"; sourceTree = ""; }; 48 | 1ADB1A5C24A99D3200D4509F /* iphone-11_1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "iphone-11_1.jpg"; sourceTree = ""; }; 49 | 1ADB1A5D24A99D3200D4509F /* iphone-11_0.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "iphone-11_0.jpg"; sourceTree = ""; }; 50 | 1ADB1A5E24A99D3200D4509F /* iphone-11_2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "iphone-11_2.jpg"; sourceTree = ""; }; 51 | 1ADB1A5F24A99D3200D4509F /* iphone-11.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iphone-11.mp4"; sourceTree = ""; }; 52 | 1ADB1A7624A99DA500D4509F /* PlayerControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerControlView.swift; sourceTree = ""; }; 53 | 1ADB1A7724A99DA500D4509F /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; 54 | 1ADB1A7824A99DA500D4509F /* PlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 1A5BB67624A345EA00368BEF /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 1A5BB67024A345EA00368BEF = { 69 | isa = PBXGroup; 70 | children = ( 71 | 1ADB1A5724A8DD5200D4509F /* README.md */, 72 | 1A5BB67B24A345EA00368BEF /* JPPictureInPictureDemo */, 73 | 1A5BB67A24A345EA00368BEF /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | 1A5BB67A24A345EA00368BEF /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 1A5BB67924A345EA00368BEF /* JPPictureInPictureDemo.app */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 1A5BB67B24A345EA00368BEF /* JPPictureInPictureDemo */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 1A5BB67C24A345EA00368BEF /* AppDelegate.swift */, 89 | 1A5BB67E24A345EA00368BEF /* SceneDelegate.swift */, 90 | 1A5BB68024A345EA00368BEF /* ViewController.swift */, 91 | 1ADB1A5524A8DB8B00D4509F /* iPhone11ViewController.swift */, 92 | 1ADB1A4724A8D8E100D4509F /* iPhone11ProViewController.swift */, 93 | 1ADB1A7224A99DA500D4509F /* Classse */, 94 | 1ADB1A5924A99D3200D4509F /* Resource */, 95 | 1A5BB68224A345EA00368BEF /* Main.storyboard */, 96 | 1A5BB68524A345EA00368BEF /* Assets.xcassets */, 97 | 1A5BB68724A345EA00368BEF /* LaunchScreen.storyboard */, 98 | 1A5BB68A24A345EA00368BEF /* Info.plist */, 99 | ); 100 | path = JPPictureInPictureDemo; 101 | sourceTree = ""; 102 | }; 103 | 1ADB1A5924A99D3200D4509F /* Resource */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 1ADB1A5A24A99D3200D4509F /* iphone-11-pro.mp4 */, 107 | 1ADB1A5B24A99D3200D4509F /* iphone-11-pro_0.jpg */, 108 | 1ADB1A5C24A99D3200D4509F /* iphone-11_1.jpg */, 109 | 1ADB1A5D24A99D3200D4509F /* iphone-11_0.jpg */, 110 | 1ADB1A5E24A99D3200D4509F /* iphone-11_2.jpg */, 111 | 1ADB1A5F24A99D3200D4509F /* iphone-11.mp4 */, 112 | ); 113 | path = Resource; 114 | sourceTree = ""; 115 | }; 116 | 1ADB1A7224A99DA500D4509F /* Classse */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 1A8B41F825B85FA300982216 /* Constont.swift */, 120 | 1A8B41F925B85FA300982216 /* FunButton.swift */, 121 | 1ADB1A7524A99DA500D4509F /* Player */, 122 | ); 123 | path = Classse; 124 | sourceTree = ""; 125 | }; 126 | 1ADB1A7524A99DA500D4509F /* Player */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 1ADB1A7824A99DA500D4509F /* PlayerViewController.swift */, 130 | 1ADB1A7624A99DA500D4509F /* PlayerControlView.swift */, 131 | 1ADB1A7724A99DA500D4509F /* PlayerView.swift */, 132 | ); 133 | path = Player; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 1A5BB67824A345EA00368BEF /* JPPictureInPictureDemo */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 1A5BB68D24A345EA00368BEF /* Build configuration list for PBXNativeTarget "JPPictureInPictureDemo" */; 142 | buildPhases = ( 143 | 1A5BB67524A345EA00368BEF /* Sources */, 144 | 1A5BB67624A345EA00368BEF /* Frameworks */, 145 | 1A5BB67724A345EA00368BEF /* Resources */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | ); 151 | name = JPPictureInPictureDemo; 152 | productName = JPPictureInPictureDemo; 153 | productReference = 1A5BB67924A345EA00368BEF /* JPPictureInPictureDemo.app */; 154 | productType = "com.apple.product-type.application"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | 1A5BB67124A345EA00368BEF /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastSwiftUpdateCheck = 1200; 163 | LastUpgradeCheck = 1200; 164 | TargetAttributes = { 165 | 1A5BB67824A345EA00368BEF = { 166 | CreatedOnToolsVersion = 12.0; 167 | }; 168 | }; 169 | }; 170 | buildConfigurationList = 1A5BB67424A345EA00368BEF /* Build configuration list for PBXProject "JPPictureInPictureDemo" */; 171 | compatibilityVersion = "Xcode 9.3"; 172 | developmentRegion = en; 173 | hasScannedForEncodings = 0; 174 | knownRegions = ( 175 | en, 176 | Base, 177 | ); 178 | mainGroup = 1A5BB67024A345EA00368BEF; 179 | productRefGroup = 1A5BB67A24A345EA00368BEF /* Products */; 180 | projectDirPath = ""; 181 | projectRoot = ""; 182 | targets = ( 183 | 1A5BB67824A345EA00368BEF /* JPPictureInPictureDemo */, 184 | ); 185 | }; 186 | /* End PBXProject section */ 187 | 188 | /* Begin PBXResourcesBuildPhase section */ 189 | 1A5BB67724A345EA00368BEF /* Resources */ = { 190 | isa = PBXResourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | 1ADB1A6B24A99D3200D4509F /* iphone-11_2.jpg in Resources */, 194 | 1ADB1A6924A99D3200D4509F /* iphone-11_1.jpg in Resources */, 195 | 1ADB1A6724A99D3200D4509F /* iphone-11-pro.mp4 in Resources */, 196 | 1A5BB68924A345EA00368BEF /* LaunchScreen.storyboard in Resources */, 197 | 1ADB1A5824A8DD5200D4509F /* README.md in Resources */, 198 | 1ADB1A6A24A99D3200D4509F /* iphone-11_0.jpg in Resources */, 199 | 1ADB1A6824A99D3200D4509F /* iphone-11-pro_0.jpg in Resources */, 200 | 1A5BB68624A345EA00368BEF /* Assets.xcassets in Resources */, 201 | 1ADB1A6C24A99D3200D4509F /* iphone-11.mp4 in Resources */, 202 | 1A5BB68424A345EA00368BEF /* Main.storyboard in Resources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXResourcesBuildPhase section */ 207 | 208 | /* Begin PBXSourcesBuildPhase section */ 209 | 1A5BB67524A345EA00368BEF /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 1A8B41FA25B85FA300982216 /* Constont.swift in Sources */, 214 | 1ADB1A7B24A99DA500D4509F /* PlayerControlView.swift in Sources */, 215 | 1A5BB68124A345EA00368BEF /* ViewController.swift in Sources */, 216 | 1ADB1A5624A8DB8B00D4509F /* iPhone11ViewController.swift in Sources */, 217 | 1ADB1A7C24A99DA500D4509F /* PlayerView.swift in Sources */, 218 | 1A8B41FB25B85FA300982216 /* FunButton.swift in Sources */, 219 | 1A5BB67D24A345EA00368BEF /* AppDelegate.swift in Sources */, 220 | 1ADB1A7D24A99DA500D4509F /* PlayerViewController.swift in Sources */, 221 | 1A5BB67F24A345EA00368BEF /* SceneDelegate.swift in Sources */, 222 | 1ADB1A4824A8D8E100D4509F /* iPhone11ProViewController.swift in Sources */, 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | }; 226 | /* End PBXSourcesBuildPhase section */ 227 | 228 | /* Begin PBXVariantGroup section */ 229 | 1A5BB68224A345EA00368BEF /* Main.storyboard */ = { 230 | isa = PBXVariantGroup; 231 | children = ( 232 | 1A5BB68324A345EA00368BEF /* Base */, 233 | ); 234 | name = Main.storyboard; 235 | sourceTree = ""; 236 | }; 237 | 1A5BB68724A345EA00368BEF /* LaunchScreen.storyboard */ = { 238 | isa = PBXVariantGroup; 239 | children = ( 240 | 1A5BB68824A345EA00368BEF /* Base */, 241 | ); 242 | name = LaunchScreen.storyboard; 243 | sourceTree = ""; 244 | }; 245 | /* End PBXVariantGroup section */ 246 | 247 | /* Begin XCBuildConfiguration section */ 248 | 1A5BB68B24A345EA00368BEF /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_ANALYZER_NONNULL = YES; 253 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 254 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 255 | CLANG_CXX_LIBRARY = "libc++"; 256 | CLANG_ENABLE_MODULES = YES; 257 | CLANG_ENABLE_OBJC_ARC = YES; 258 | CLANG_ENABLE_OBJC_WEAK = YES; 259 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_COMMA = YES; 262 | CLANG_WARN_CONSTANT_CONVERSION = YES; 263 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 271 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 272 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 274 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 275 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 276 | CLANG_WARN_STRICT_PROTOTYPES = YES; 277 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 278 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 279 | CLANG_WARN_UNREACHABLE_CODE = YES; 280 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 281 | COPY_PHASE_STRIP = NO; 282 | DEBUG_INFORMATION_FORMAT = dwarf; 283 | ENABLE_STRICT_OBJC_MSGSEND = YES; 284 | ENABLE_TESTABILITY = YES; 285 | GCC_C_LANGUAGE_STANDARD = gnu11; 286 | GCC_DYNAMIC_NO_PIC = NO; 287 | GCC_NO_COMMON_BLOCKS = YES; 288 | GCC_OPTIMIZATION_LEVEL = 0; 289 | GCC_PREPROCESSOR_DEFINITIONS = ( 290 | "DEBUG=1", 291 | "$(inherited)", 292 | ); 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 300 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 301 | MTL_FAST_MATH = YES; 302 | ONLY_ACTIVE_ARCH = YES; 303 | SDKROOT = iphoneos; 304 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 305 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 306 | }; 307 | name = Debug; 308 | }; 309 | 1A5BB68C24A345EA00368BEF /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_ENABLE_OBJC_WEAK = YES; 320 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 321 | CLANG_WARN_BOOL_CONVERSION = YES; 322 | CLANG_WARN_COMMA = YES; 323 | CLANG_WARN_CONSTANT_CONVERSION = YES; 324 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 325 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 326 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 327 | CLANG_WARN_EMPTY_BODY = YES; 328 | CLANG_WARN_ENUM_CONVERSION = YES; 329 | CLANG_WARN_INFINITE_RECURSION = YES; 330 | CLANG_WARN_INT_CONVERSION = YES; 331 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 333 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 335 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 336 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 337 | CLANG_WARN_STRICT_PROTOTYPES = YES; 338 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 339 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 340 | CLANG_WARN_UNREACHABLE_CODE = YES; 341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 342 | COPY_PHASE_STRIP = NO; 343 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 344 | ENABLE_NS_ASSERTIONS = NO; 345 | ENABLE_STRICT_OBJC_MSGSEND = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu11; 347 | GCC_NO_COMMON_BLOCKS = YES; 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 355 | MTL_ENABLE_DEBUG_INFO = NO; 356 | MTL_FAST_MATH = YES; 357 | SDKROOT = iphoneos; 358 | SWIFT_COMPILATION_MODE = wholemodule; 359 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 360 | VALIDATE_PRODUCT = YES; 361 | }; 362 | name = Release; 363 | }; 364 | 1A5BB68E24A345EA00368BEF /* Debug */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 368 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 369 | CODE_SIGN_STYLE = Automatic; 370 | DEVELOPMENT_TEAM = FH4WHYK8ZQ; 371 | INFOPLIST_FILE = JPPictureInPictureDemo/Info.plist; 372 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 373 | LD_RUNPATH_SEARCH_PATHS = ( 374 | "$(inherited)", 375 | "@executable_path/Frameworks", 376 | ); 377 | PRODUCT_BUNDLE_IDENTIFIER = zhoujianping.JPPictureInPictureDemo; 378 | PRODUCT_NAME = "$(TARGET_NAME)"; 379 | SWIFT_VERSION = 5.0; 380 | TARGETED_DEVICE_FAMILY = 1; 381 | }; 382 | name = Debug; 383 | }; 384 | 1A5BB68F24A345EA00368BEF /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 388 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 389 | CODE_SIGN_STYLE = Automatic; 390 | DEVELOPMENT_TEAM = FH4WHYK8ZQ; 391 | INFOPLIST_FILE = JPPictureInPictureDemo/Info.plist; 392 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 393 | LD_RUNPATH_SEARCH_PATHS = ( 394 | "$(inherited)", 395 | "@executable_path/Frameworks", 396 | ); 397 | PRODUCT_BUNDLE_IDENTIFIER = zhoujianping.JPPictureInPictureDemo; 398 | PRODUCT_NAME = "$(TARGET_NAME)"; 399 | SWIFT_VERSION = 5.0; 400 | TARGETED_DEVICE_FAMILY = 1; 401 | }; 402 | name = Release; 403 | }; 404 | /* End XCBuildConfiguration section */ 405 | 406 | /* Begin XCConfigurationList section */ 407 | 1A5BB67424A345EA00368BEF /* Build configuration list for PBXProject "JPPictureInPictureDemo" */ = { 408 | isa = XCConfigurationList; 409 | buildConfigurations = ( 410 | 1A5BB68B24A345EA00368BEF /* Debug */, 411 | 1A5BB68C24A345EA00368BEF /* Release */, 412 | ); 413 | defaultConfigurationIsVisible = 0; 414 | defaultConfigurationName = Release; 415 | }; 416 | 1A5BB68D24A345EA00368BEF /* Build configuration list for PBXNativeTarget "JPPictureInPictureDemo" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | 1A5BB68E24A345EA00368BEF /* Debug */, 420 | 1A5BB68F24A345EA00368BEF /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | /* End XCConfigurationList section */ 426 | }; 427 | rootObject = 1A5BB67124A345EA00368BEF /* Project object */; 428 | } 429 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo.xcodeproj/project.xcworkspace/xcuserdata/zhoujianping.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo.xcodeproj/project.xcworkspace/xcuserdata/zhoujianping.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /JPPictureInPictureDemo.xcodeproj/xcuserdata/zhoujianping.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo.xcodeproj/xcuserdata/zhoujianping.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | JPPictureInPictureDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/.DS_Store -------------------------------------------------------------------------------- /JPPictureInPictureDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/24. 6 | // 7 | 8 | import UIKit 9 | 10 | @UIApplicationMain 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/com_left_white_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "com_left_white_icon@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "com_left_white_icon@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/com_left_white_icon.imageset/com_left_white_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Assets.xcassets/com_left_white_icon.imageset/com_left_white_icon@2x.png -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/com_left_white_icon.imageset/com_left_white_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Assets.xcassets/com_left_white_icon.imageset/com_left_white_icon@3x.png -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/find_play_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "find_play_icon@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "find_play_icon@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/find_play_icon.imageset/find_play_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Assets.xcassets/find_play_icon.imageset/find_play_icon@2x.png -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/find_play_icon.imageset/find_play_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Assets.xcassets/find_play_icon.imageset/find_play_icon@3x.png -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/find_suspend_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "find_suspend_icon@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "find_suspend_icon@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/find_suspend_icon.imageset/find_suspend_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Assets.xcassets/find_suspend_icon.imageset/find_suspend_icon@2x.png -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Assets.xcassets/find_suspend_icon.imageset/find_suspend_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Assets.xcassets/find_suspend_icon.imageset/find_suspend_icon@3x.png -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Classse/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Classse/.DS_Store -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Classse/Constont.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constont.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/2/28. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let ScreenScale: CGFloat = UIScreen.main.scale 12 | 13 | let PortraitScreenWidth: CGFloat = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height) 14 | let PortraitScreenHeight: CGFloat = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height) 15 | let PortraitScreenSize: CGSize = CGSize(width: PortraitScreenWidth, height: PortraitScreenHeight) 16 | let PortraitScreenBounds: CGRect = CGRect(origin: .zero, size: PortraitScreenSize) 17 | 18 | let LandscapeScreenWidth: CGFloat = PortraitScreenHeight 19 | let LandscapeScreenHeight: CGFloat = PortraitScreenWidth 20 | let LandscapeScreenSize: CGSize = CGSize(width: LandscapeScreenWidth, height: LandscapeScreenHeight) 21 | let LandscapeScreenBounds: CGRect = CGRect(origin: .zero, size: LandscapeScreenSize) 22 | 23 | let IsBangsScreen: Bool = PortraitScreenHeight > 736.0 24 | 25 | let BaseTabBarH: CGFloat = 49.0 26 | let TabBarH: CGFloat = IsBangsScreen ? 83.0 : BaseTabBarH 27 | let DiffTabBarH: CGFloat = TabBarH - BaseTabBarH 28 | 29 | let BaseStatusBarH: CGFloat = 20.0 30 | let StatusBarH: CGFloat = IsBangsScreen ? 44.0 : BaseStatusBarH 31 | let DiffStatusBarH: CGFloat = StatusBarH - BaseStatusBarH 32 | 33 | let NavBarH: CGFloat = 44.0 34 | let NavTopMargin: CGFloat = StatusBarH + NavBarH 35 | 36 | let BasisWScale: CGFloat = PortraitScreenWidth / 375.0 37 | let BasisHScale: CGFloat = PortraitScreenHeight / 667.0 38 | 39 | func swapValues(_ a: inout T, _ b: inout T) { 40 | (a, b) = (b, a) 41 | } 42 | 43 | func ScaleValue(_ value: CGFloat) -> CGFloat { 44 | value * BasisWScale 45 | } 46 | 47 | func ScaleValue(_ value: Double) -> CGFloat { 48 | CGFloat(value) * BasisWScale 49 | } 50 | 51 | func ScaleValue(_ value: Float) -> CGFloat { 52 | CGFloat(value) * BasisWScale 53 | } 54 | 55 | func ScaleValue(_ value: Int) -> CGFloat { 56 | CGFloat(value) * BasisWScale 57 | } 58 | 59 | func HalfDiffValue(_ superValue: CGFloat, _ subValue: CGFloat) -> CGFloat { 60 | (superValue - subValue) * 0.5 61 | } 62 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Classse/FunButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunButton.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/24. 6 | // 7 | 8 | import UIKit 9 | 10 | class FunButton: UIButton { 11 | private let bgView: UIView = UIView() 12 | private let label0: UILabel = UILabel() 13 | private let label1: UILabel = UILabel() 14 | private let label2: UILabel = UILabel() 15 | private lazy var impactFeedbacker: UIImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light) 16 | 17 | private var _isTouching: Bool = false 18 | // private(set):读公有,写私有 19 | private(set) var isTouching: Bool { 20 | set { 21 | if _isTouching == newValue { return } 22 | _isTouching = newValue 23 | if newValue { 24 | label1.text = "😝" 25 | impactFeedbacker.prepare() 26 | impactFeedbacker.impactOccurred() 27 | let transform = CGAffineTransform(scaleX: 0.8, y: 0.8) 28 | let transform0 = CGAffineTransform(translationX: 0, y: -12).concatenating(transform) 29 | let transform2 = CGAffineTransform(translationX: 0, y: 12).concatenating(transform) 30 | UIView.animate(withDuration: 0.28, animations: { 31 | self.bgView.transform = CGAffineTransform(scaleX: 1.08, y: 1.08) 32 | self.label0.transform = transform0 33 | self.label1.transform = CGAffineTransform(scaleX: 1, y: 1) 34 | self.label2.transform = transform2 35 | }) 36 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.12) { [weak self] in 37 | guard let self = self, self.isTouching else {return} 38 | self.label0.text = "Free" 39 | } 40 | } else { 41 | label0.text = "Tap" 42 | label1.text = "😛" 43 | let transform = CGAffineTransform.identity 44 | UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 1, options: [], animations: { 45 | self.bgView.transform = transform 46 | self.label0.transform = transform 47 | self.label1.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) 48 | self.label2.transform = transform 49 | }, completion: nil) 50 | } 51 | } 52 | get { _isTouching } 53 | } 54 | 55 | var touchUpInside: (() -> ())? 56 | 57 | class func build(_ touchUpInside: (() -> Void)? = nil) -> FunButton { 58 | let btn = self.init(type: .custom) 59 | btn.frame = CGRect(x: 0, y: 0, width: 150, height: 150) 60 | btn._setupUI() 61 | btn._setupAction() 62 | btn.touchUpInside = touchUpInside 63 | return btn 64 | } 65 | } 66 | 67 | private extension FunButton { 68 | func _setupUI() { 69 | bgView.frame = bounds 70 | bgView.layer.cornerRadius = 20 71 | bgView.layer.borderWidth = 0 72 | bgView.backgroundColor = .yellow 73 | bgView.isUserInteractionEnabled = false 74 | addSubview(bgView) 75 | 76 | let font = UIFont(name: "Bradley Hand", size: 27)! 77 | let textColor = UIColor.red 78 | 79 | label1.font = .systemFont(ofSize: 54) 80 | label1.frame = CGRect(x: 0, y: (150 - label1.font.lineHeight) * 0.5, width: 150, height: label1.font.lineHeight) 81 | label1.text = "😛" 82 | label1.textAlignment = .center 83 | label1.textColor = textColor 84 | addSubview(label1) 85 | label1.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) 86 | 87 | label0.frame = CGRect(x: 0, y: (label1.frame.origin.y - font.lineHeight - 5), width: 150, height: font.lineHeight) 88 | label0.text = "Tap" 89 | label0.textAlignment = .center 90 | label0.font = font 91 | label0.textColor = textColor 92 | insertSubview(label0, belowSubview: label1) 93 | 94 | label2.frame = CGRect(x: 0, y: (label1.frame.maxY + 5), width: 150, height: font.lineHeight) 95 | label2.text = "Me" 96 | label2.textAlignment = .center 97 | label2.font = font 98 | label2.textColor = textColor 99 | insertSubview(label2, belowSubview: label1) 100 | } 101 | 102 | func _setupAction() { 103 | addTarget(self, action: #selector(_beginTouch), for: .touchDown) 104 | addTarget(self, action: #selector(_beginTouch), for: .touchDragInside) 105 | addTarget(self, action: #selector(_endTouch), for: .touchDragOutside) 106 | addTarget(self, action: #selector(_endTouch), for: .touchUpOutside) 107 | addTarget(self, action: #selector(_endTouch), for: .touchCancel) 108 | addTarget(self, action: #selector(_touchUpInside), for: .touchUpInside) 109 | } 110 | 111 | @objc func _beginTouch() { 112 | isTouching = true 113 | } 114 | 115 | @objc func _endTouch() { 116 | isTouching = false 117 | } 118 | 119 | @objc func _touchUpInside() { 120 | isTouching = false 121 | touchUpInside?() 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Classse/Player/PlayerControlView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerControlView.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/28. 6 | // 7 | 8 | import UIKit 9 | import AVKit 10 | 11 | class PlayerControlView: UIView { 12 | 13 | let progressView: UIProgressView 14 | 15 | var progress: Float { 16 | set { 17 | progressView.progress = newValue < 0 ? 0 : (newValue > 1 ? 1 : newValue) 18 | 19 | let alpha : CGFloat = (newValue <= 0 || newValue >= 1) ? 0 : 1 20 | guard progressView.alpha != alpha else { return } 21 | UIView.animate(withDuration: 0.5) { 22 | self.progressView.alpha = alpha 23 | } 24 | } 25 | get { progressView.progress } 26 | } 27 | 28 | let blurView: UIVisualEffectView 29 | 30 | let resumeBtn: UIButton 31 | 32 | var pipBtn: UIButton? 33 | 34 | fileprivate var _isShowResumeBtn: Bool = true 35 | var isShowResumeBtn: Bool { 36 | set { 37 | if _isShowResumeBtn == newValue { return } 38 | _isShowResumeBtn = newValue 39 | UIView.animate(withDuration: 0.5) { 40 | self.blurView.alpha = newValue ? 1 : 0 41 | } 42 | } 43 | get { _isShowResumeBtn } 44 | } 45 | 46 | override init(frame: CGRect) { 47 | self.progressView = UIProgressView(frame: CGRect(x: 0, y: frame.height - 3, width: frame.width, height: 3)) 48 | self.progressView.trackTintColor = .lightGray 49 | self.progressView.progressTintColor = .white 50 | self.progressView.progress = 0 51 | self.progressView.isUserInteractionEnabled = false 52 | self.progressView.alpha = 0 53 | 54 | self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) 55 | self.blurView.layer.cornerRadius = 8 56 | self.blurView.layer.masksToBounds = true 57 | 58 | let playIcon = UIImage(named: "find_play_icon")?.withRenderingMode(.alwaysOriginal) 59 | let pauseIcon = UIImage(named: "find_suspend_icon")?.withRenderingMode(.alwaysOriginal) 60 | self.resumeBtn = UIButton(type: .system) 61 | self.resumeBtn.setImage(playIcon, for: .normal) 62 | self.resumeBtn.setImage(playIcon, for: [.normal, .highlighted]) 63 | self.resumeBtn.setImage(pauseIcon, for: .selected) 64 | self.resumeBtn.setImage(pauseIcon, for: [.selected, .highlighted]) 65 | self.resumeBtn.tintColor = .clear 66 | self.resumeBtn.frame = CGRect(x: 0, y: 0, width: NavBarH, height: NavBarH) 67 | self.blurView.contentView.addSubview(self.resumeBtn) 68 | 69 | if AVPictureInPictureController.isPictureInPictureSupported() { 70 | var startImage = AVPictureInPictureController.pictureInPictureButtonStartImage 71 | UIGraphicsBeginImageContextWithOptions(startImage.size, false, 0) 72 | UIColor.white.setFill() 73 | UIRectFill(CGRect(origin: .zero, size: startImage.size)) 74 | startImage.draw(at: .zero, blendMode: .destinationIn, alpha: 1) 75 | startImage = UIGraphicsGetImageFromCurrentImageContext() ?? startImage 76 | UIGraphicsEndImageContext() 77 | startImage = startImage.withRenderingMode(.alwaysOriginal) 78 | 79 | var stopImage = AVPictureInPictureController.pictureInPictureButtonStopImage 80 | UIGraphicsBeginImageContextWithOptions(stopImage.size, false, 0) 81 | UIColor.white.setFill() 82 | UIRectFill(CGRect(origin: .zero, size: stopImage.size)) 83 | stopImage.draw(at: .zero, blendMode: .destinationIn, alpha: 1) 84 | stopImage = UIGraphicsGetImageFromCurrentImageContext() ?? stopImage 85 | UIGraphicsEndImageContext() 86 | stopImage = stopImage.withRenderingMode(.alwaysOriginal) 87 | 88 | let pipBtn = UIButton(type: .system) 89 | pipBtn.setImage(startImage, for: .normal) 90 | pipBtn.setImage(startImage, for: [.normal, .highlighted]) 91 | pipBtn.setImage(stopImage, for: .selected) 92 | pipBtn.setImage(stopImage, for: [.selected, .highlighted]) 93 | pipBtn.tintColor = .clear 94 | pipBtn.frame = CGRect(x: self.resumeBtn.frame.maxX, y: 0, width: NavBarH, height: NavBarH) 95 | self.blurView.contentView.addSubview(pipBtn) 96 | self.pipBtn = pipBtn 97 | 98 | self.blurView.frame = CGRect(x: 8, y: frame.height - NavBarH - 10, width: pipBtn.frame.maxX, height: NavBarH) 99 | 100 | } else { 101 | self.blurView.frame = CGRect(x: 8, y: frame.height - NavBarH - 10, width: self.resumeBtn.frame.width, height: NavBarH) 102 | } 103 | 104 | super.init(frame: frame) 105 | addSubview(blurView) 106 | addSubview(progressView) 107 | } 108 | 109 | required init?(coder: NSCoder) { 110 | fatalError("init(coder:) has not been implemented") 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Classse/Player/PlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerView.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/25. 6 | // 7 | 8 | import UIKit 9 | import AVKit 10 | 11 | class PlayerView: UIView { 12 | 13 | var playerLayer: AVPlayerLayer = AVPlayerLayer() 14 | let controlView: PlayerControlView 15 | 16 | var urlAsset: AVURLAsset? 17 | var playerItem: AVPlayerItem? 18 | var player: AVPlayer? 19 | 20 | fileprivate var timeObserver: Any? 21 | 22 | fileprivate weak var pipCtr: AVPictureInPictureController? 23 | 24 | fileprivate var _isPlayDone: Bool = false 25 | 26 | fileprivate var _timer: Timer? 27 | 28 | override init(frame: CGRect) { 29 | self.controlView = PlayerControlView(frame: CGRect(origin: .zero, size: frame.size)) 30 | super.init(frame: frame) 31 | _setupUI() 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | deinit { 39 | _removeTimer() 40 | 41 | if let player = player { 42 | NotificationCenter.default.removeObserver(self) 43 | 44 | player.removeObserver(self, forKeyPath: #keyPath(AVPlayer.rate)) 45 | 46 | if let timeObserver = timeObserver { 47 | player.removeTimeObserver(timeObserver) 48 | self.timeObserver = nil 49 | } 50 | 51 | player.replaceCurrentItem(with: nil) 52 | } 53 | 54 | if let pipCtr = pipCtr { 55 | pipCtr.removeObserver(self, forKeyPath: #keyPath(AVPictureInPictureController.isPictureInPicturePossible)) 56 | pipCtr.removeObserver(self, forKeyPath: #keyPath(AVPictureInPictureController.isPictureInPictureActive)) 57 | } 58 | } 59 | } 60 | 61 | // MARK:- API 62 | extension PlayerView { 63 | func setupVideoURL(_ videoURL: URL, pipCtr: AVPictureInPictureController?) { 64 | let asset = AVURLAsset(url: videoURL) 65 | urlAsset = asset 66 | playerItem = AVPlayerItem(asset: asset) 67 | player = AVPlayer(playerItem: playerItem) 68 | if let player = player { 69 | playerLayer.player = player 70 | 71 | timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 1), queue: DispatchQueue.main) { [weak self] (time) in 72 | guard let currentItem = self?.playerItem else {return} 73 | let loadedRanges = currentItem.seekableTimeRanges 74 | if loadedRanges.count > 0 && currentItem.duration.timescale != 0 { 75 | let progress = CMTimeGetSeconds(time) / CMTimeGetSeconds(currentItem.duration) 76 | self?.controlView.progress = Float(progress) 77 | } else { 78 | self?.controlView.progress = 0 79 | } 80 | } 81 | 82 | player.addObserver(self, forKeyPath: #keyPath(AVPlayer.rate), options: .new, context: nil) 83 | 84 | NotificationCenter.default.addObserver(self, selector: #selector(_playDidEnd), name: NSNotification.Name(rawValue: "AVPlayerItemDidPlayToEndTimeNotification"), object: playerItem) 85 | } 86 | 87 | self.pipCtr = pipCtr 88 | if let pipCtr = pipCtr { 89 | pipCtr.addObserver(self, forKeyPath: #keyPath(AVPictureInPictureController.isPictureInPicturePossible), options: .new, context: nil) 90 | pipCtr.addObserver(self, forKeyPath: #keyPath(AVPictureInPictureController.isPictureInPictureActive), options: .new, context: nil) 91 | } 92 | } 93 | } 94 | 95 | // MARK:- 私有API 96 | private extension PlayerView { 97 | func _setupUI() { 98 | clipsToBounds = true 99 | backgroundColor = .black 100 | 101 | playerLayer.frame = self.bounds 102 | playerLayer.videoGravity = .resizeAspect 103 | layer.addSublayer(playerLayer) 104 | 105 | controlView.resumeBtn.addTarget(self, action: #selector(_resumeOrPause), for: .touchUpInside) 106 | controlView.pipBtn?.addTarget(self, action: #selector(_togglePictureInPicture), for: .touchUpInside) 107 | let tapGR = UITapGestureRecognizer(target: self, action: #selector(_tap)) 108 | tapGR.delegate = self 109 | controlView.addGestureRecognizer(tapGR) 110 | addSubview(controlView) 111 | } 112 | } 113 | 114 | // MARK:- 按钮点击事件 115 | private extension PlayerView { 116 | @objc func _resumeOrPause() { 117 | _removeTimer() 118 | controlView.isShowResumeBtn = true 119 | 120 | guard let player = player else { return } 121 | 122 | controlView.resumeBtn.isSelected = !controlView.resumeBtn.isSelected 123 | if controlView.resumeBtn.isSelected { 124 | if _isPlayDone { 125 | _isPlayDone = false 126 | player.seek(to: .zero) 127 | } 128 | player.play() 129 | _addTimer() 130 | } else { 131 | player.pause() 132 | } 133 | } 134 | 135 | @objc func _togglePictureInPicture() { 136 | _addTimer() 137 | 138 | guard let pipCtr = pipCtr else { return } 139 | 140 | if pipCtr.isPictureInPictureActive { 141 | controlView.pipBtn?.isSelected = false 142 | pipCtr.stopPictureInPicture() 143 | } else { 144 | if let playerVC = playerVC_ { 145 | playerVC.stopPictureInPicture { [weak self] in 146 | self?.controlView.pipBtn?.isSelected = true 147 | self?.pipCtr?.startPictureInPicture() 148 | } 149 | } else { 150 | controlView.pipBtn?.isSelected = true 151 | pipCtr.startPictureInPicture() 152 | } 153 | } 154 | } 155 | } 156 | 157 | // MARK:- 事件监听 158 | extension PlayerView { 159 | @objc fileprivate func _playDidEnd() { 160 | _isPlayDone = true 161 | controlView.resumeBtn.isSelected = false 162 | 163 | _removeTimer() 164 | controlView.isShowResumeBtn = true 165 | } 166 | 167 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { 168 | 169 | if let player = player { 170 | let isSelected = player.rate > 0 171 | if controlView.resumeBtn.isSelected != isSelected { 172 | _resumeOrPause() 173 | } 174 | } 175 | 176 | guard let pipCtr = pipCtr, let pipBtn = controlView.pipBtn else { return } 177 | pipBtn.isEnabled = pipCtr.isPictureInPicturePossible 178 | pipBtn.isSelected = pipCtr.isPictureInPictureActive 179 | } 180 | 181 | @objc fileprivate func _tap() { 182 | controlView.isShowResumeBtn = !controlView.isShowResumeBtn 183 | if controlView.isShowResumeBtn { 184 | _addTimer() 185 | } 186 | } 187 | } 188 | 189 | // MARK:- 190 | extension PlayerView: UIGestureRecognizerDelegate { 191 | override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 192 | if controlView.isShowResumeBtn { 193 | let location = gestureRecognizer.location(in: controlView) 194 | if controlView.blurView.frame.contains(location) { 195 | return false 196 | } 197 | } 198 | return true 199 | } 200 | } 201 | 202 | // MARK:- Timer 203 | private extension PlayerView { 204 | func _addTimer() { 205 | _removeTimer() 206 | _timer = Timer.init(timeInterval: 5.0, repeats: false, block: { [weak self] (timer) in 207 | self?.controlView.isShowResumeBtn = false 208 | }) 209 | guard let timer = _timer else {return} 210 | RunLoop.main.add(timer, forMode: .common) 211 | } 212 | 213 | func _removeTimer() { 214 | guard let timer = _timer else { return } 215 | timer.invalidate() 216 | _timer = nil 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Classse/Player/PlayerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerViewController.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/25. 6 | // 7 | 8 | import UIKit 9 | import AVKit 10 | 11 | var playerVC_: PlayerViewController? 12 | 13 | class PlayerViewController: UIViewController { 14 | 15 | var videoPath: String! 16 | var playerView: PlayerView! 17 | 18 | var pipCtr: AVPictureInPictureController? 19 | private var stopPipComplete: (() -> ())? 20 | 21 | private weak var navCtr: UINavigationController? 22 | 23 | private let backBtn: UIButton = { 24 | let backBtn = UIButton(type: .system) 25 | backBtn.setImage(UIImage(named: "com_left_white_icon"), for: .normal) 26 | backBtn.frame = CGRect(x: 0, y: StatusBarH, width: NavBarH, height: NavBarH) 27 | backBtn.tintColor = .white 28 | return backBtn 29 | }() 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | view.clipsToBounds = true 34 | backBtn.addTarget(self, action: #selector(_backAction), for: .touchUpInside) 35 | } 36 | 37 | override func viewWillAppear(_ animated: Bool) { 38 | super.viewWillAppear(animated) 39 | view.addSubview(backBtn) 40 | 41 | navigationController?.setNavigationBarHidden(true, animated: animated) 42 | navigationController?.interactivePopGestureRecognizer?.delegate = nil 43 | 44 | navCtr = navigationController 45 | 46 | if playerVC_ == self { 47 | pipCtr?.stopPictureInPicture() 48 | } 49 | } 50 | 51 | @objc private func _backAction() { 52 | navigationController?.popViewController(animated: true) 53 | } 54 | } 55 | 56 | // MARK:- API 57 | extension PlayerViewController { 58 | func createPlayerView(_ frame: CGRect, videoURL: URL) { 59 | playerView = PlayerView(frame: frame) 60 | 61 | if AVPictureInPictureController.isPictureInPictureSupported() { 62 | do { 63 | try AVAudioSession.sharedInstance().setCategory(.playback) 64 | try AVAudioSession.sharedInstance().setActive(true, options: []) 65 | } catch { 66 | print("AVAudioSession发生错误") 67 | } 68 | 69 | pipCtr = AVPictureInPictureController(playerLayer: playerView.playerLayer) 70 | pipCtr?.delegate = self 71 | } 72 | 73 | playerView.setupVideoURL(videoURL, pipCtr: pipCtr) 74 | } 75 | 76 | func stopPictureInPicture(_ complete: (()->())?) { 77 | if let pipCtr = pipCtr, pipCtr.isPictureInPictureActive { 78 | stopPipComplete = complete 79 | pipCtr.stopPictureInPicture() 80 | } else { 81 | stopPipComplete = nil 82 | } 83 | } 84 | } 85 | 86 | // MARK:- 87 | extension PlayerViewController: AVPictureInPictureControllerDelegate { 88 | /** 89 | @method pictureInPictureControllerWillStartPictureInPicture: 90 | @param pictureInPictureController 91 | The Picture in Picture controller. 92 | @abstract Delegate can implement this method to be notified when Picture in Picture will start. 93 | */ 94 | func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 95 | print("pictureInPictureControllerWillStartPictureInPicture") 96 | playerVC_ = self 97 | navigationController?.popViewController(animated: true) 98 | } 99 | 100 | 101 | /** 102 | @method pictureInPictureControllerDidStartPictureInPicture: 103 | @param pictureInPictureController 104 | The Picture in Picture controller. 105 | @abstract Delegate can implement this method to be notified when Picture in Picture did start. 106 | */ 107 | func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 108 | print("pictureInPictureControllerDidStartPictureInPicture") 109 | } 110 | 111 | 112 | /** 113 | @method pictureInPictureController:failedToStartPictureInPictureWithError: 114 | @param pictureInPictureController 115 | The Picture in Picture controller. 116 | @param error 117 | An error describing why it failed. 118 | @abstract Delegate can implement this method to be notified when Picture in Picture failed to start. 119 | */ 120 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { 121 | print("failedToStartPictureInPictureWithError") 122 | } 123 | 124 | 125 | /** 126 | @method pictureInPictureControllerWillStopPictureInPicture: 127 | @param pictureInPictureController 128 | The Picture in Picture controller. 129 | @abstract Delegate can implement this method to be notified when Picture in Picture will stop. 130 | */ 131 | func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 132 | print("pictureInPictureControllerWillStopPictureInPicture") 133 | } 134 | 135 | 136 | /** 137 | @method pictureInPictureControllerDidStopPictureInPicture: 138 | @param pictureInPictureController 139 | The Picture in Picture controller. 140 | @abstract Delegate can implement this method to be notified when Picture in Picture did stop. 141 | */ 142 | func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 143 | print("pictureInPictureControllerDidStopPictureInPicture") 144 | playerVC_ = nil 145 | stopPipComplete?() 146 | stopPipComplete = nil 147 | } 148 | 149 | 150 | /** 151 | @method pictureInPictureController:restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: 152 | @param pictureInPictureController 153 | The Picture in Picture controller. 154 | @param completionHandler 155 | The completion handler the delegate needs to call after restore. 156 | @abstract Delegate can implement this method to restore the user interface before Picture in Picture stops. 157 | */ 158 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { 159 | print("restoreUserInterfaceForPictureInPictureStopWithCompletionHandler") 160 | 161 | if stopPipComplete == nil, let navCtr = navCtr, !navCtr.viewControllers.contains(self) { 162 | 163 | playerVC_ = nil 164 | navCtr.pushViewController(self, animated: true) 165 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15) { 166 | completionHandler(true) 167 | } 168 | return 169 | } 170 | 171 | completionHandler(true) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UIBackgroundModes 45 | 46 | audio 47 | 48 | UILaunchStoryboardName 49 | LaunchScreen 50 | UIMainStoryboardFile 51 | Main 52 | UIRequiredDeviceCapabilities 53 | 54 | armv7 55 | 56 | UISupportedInterfaceOrientations 57 | 58 | UIInterfaceOrientationPortrait 59 | 60 | UISupportedInterfaceOrientations~ipad 61 | 62 | UIInterfaceOrientationPortrait 63 | UIInterfaceOrientationPortraitUpsideDown 64 | UIInterfaceOrientationLandscapeLeft 65 | UIInterfaceOrientationLandscapeRight 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Resource/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Resource/.DS_Store -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Resource/iphone-11-pro.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Resource/iphone-11-pro.mp4 -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Resource/iphone-11-pro_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Resource/iphone-11-pro_0.jpg -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Resource/iphone-11.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Resource/iphone-11.mp4 -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Resource/iphone-11_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Resource/iphone-11_0.jpg -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Resource/iphone-11_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Resource/iphone-11_1.jpg -------------------------------------------------------------------------------- /JPPictureInPictureDemo/Resource/iphone-11_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rogue24/JPPictureInPictureDemo/5c26bb4d30972830f4771a911a52fcec090a85bd/JPPictureInPictureDemo/Resource/iphone-11_2.jpg -------------------------------------------------------------------------------- /JPPictureInPictureDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/24. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/24. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | // Do any additional setup after loading the view. 15 | 16 | title = "Hi" 17 | 18 | let btn0 = FunButton.build { [weak self] in self?.pushPlayerVC(false) } 19 | btn0.center = CGPoint(x: UIScreen.main.bounds.width * 0.5, y: UIScreen.main.bounds.height * 0.3) 20 | view.addSubview(btn0) 21 | 22 | let btn1 = FunButton.build { [weak self] in self?.pushPlayerVC(true) } 23 | btn1.center = CGPoint(x: UIScreen.main.bounds.width * 0.5, y: UIScreen.main.bounds.height * 0.7) 24 | view.addSubview(btn1) 25 | } 26 | 27 | func pushPlayerVC(_ isPro: Bool) { 28 | guard let navCtr = self.navigationController else {return} 29 | if let playerVC = playerVC_ { 30 | let videoPath = isPro ? iPhone11ProViewController.videoPath() : iPhone11ViewController.videoPath() 31 | if playerVC.videoPath == videoPath { 32 | navCtr.pushViewController(playerVC, animated: true) 33 | return 34 | } 35 | } 36 | let playerVC = isPro ? iPhone11ProViewController() : iPhone11ViewController() 37 | navCtr.pushViewController(playerVC, animated: true) 38 | } 39 | 40 | override func viewWillAppear(_ animated: Bool) { 41 | super.viewWillAppear(animated) 42 | navigationController?.setNavigationBarHidden(false, animated: animated) 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/iPhone11ProViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iPhone11ProViewController.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/28. 6 | // 7 | 8 | import UIKit 9 | 10 | class iPhone11ProViewController: PlayerViewController { 11 | 12 | class func videoPath() -> String { Bundle.main.path(forResource: "iphone-11-pro", ofType: "mp4")! } 13 | 14 | private var _isDidAppear = false 15 | 16 | private let bgImgView: UIImageView = { 17 | let bgImgView = UIImageView() 18 | bgImgView.backgroundColor = .black 19 | return bgImgView 20 | }() 21 | 22 | private let iPhoneLabel: UILabel = { 23 | let iPhoneLabel = UILabel() 24 | iPhoneLabel.font = .boldSystemFont(ofSize: 40) 25 | iPhoneLabel.textColor = .white 26 | iPhoneLabel.text = "iPhone 11 Pro" 27 | iPhoneLabel.textAlignment = .right 28 | return iPhoneLabel 29 | }() 30 | 31 | private let subLabel: UILabel = { 32 | let str: String = "Pro 如其名。" 33 | let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 30), NSAttributedString.Key.foregroundColor: UIColor.white] 34 | let attStr = NSMutableAttributedString(string: str, attributes: attributes) 35 | let range = (str as NSString).range(of: "Pro") 36 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: UIColor(red: 90.0 / 255.0, green: 104.0 / 255.0, blue: 90.0 / 255.0, alpha: 1)], range: range) 37 | 38 | let subLabel = UILabel() 39 | subLabel.attributedText = attStr 40 | subLabel.textAlignment = .right 41 | return subLabel 42 | }() 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | _setupUI() 47 | } 48 | 49 | override func viewWillAppear(_ animated: Bool) { 50 | super.viewWillAppear(animated) 51 | 52 | if _isDidAppear { return } 53 | 54 | let bgImgViewW = PortraitScreenWidth 55 | let bgImgViewY = playerView.frame.maxY - ScaleValue(60) 56 | DispatchQueue.global().async { 57 | guard let bgImage = UIImage(contentsOfFile: Bundle.main.path(forResource: "iphone-11-pro_0", ofType: "jpg")!) else {return} 58 | 59 | let imageWhScale = bgImage.size.height / bgImage.size.width 60 | let bgImgViewFrame = CGRect(x: 0, 61 | y: bgImgViewY, 62 | width: bgImgViewW, 63 | height: bgImgViewW * imageWhScale) 64 | 65 | DispatchQueue.main.async { 66 | self.bgImgView.frame = bgImgViewFrame 67 | self.bgImgView.image = bgImage 68 | self._loopAnimation(true, 0) 69 | UIView.animate(withDuration: 7) { 70 | self.bgImgView.alpha = 1 71 | } 72 | } 73 | } 74 | } 75 | 76 | override func viewDidAppear(_ animated: Bool) { 77 | super.viewDidAppear(animated) 78 | 79 | if _isDidAppear { return } 80 | _isDidAppear = true 81 | 82 | iPhoneLabel.frame = CGRect(x: -16, y: playerView.frame.maxY + 12, width: PortraitScreenWidth, height: iPhoneLabel.font.lineHeight) 83 | subLabel.frame = CGRect(x: -16, y: iPhoneLabel.frame.maxY + 12, width: PortraitScreenWidth, height: subLabel.font.lineHeight) 84 | 85 | UIView.animate(withDuration: 3, delay: 1, options: [], animations: { 86 | self.iPhoneLabel.alpha = 1 87 | }, completion: { finish in 88 | guard finish else { return } 89 | let str: String = "iPhone 11 Pro" 90 | let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: self.iPhoneLabel.font!, NSAttributedString.Key.foregroundColor: self.iPhoneLabel.textColor!] 91 | let attStr = NSMutableAttributedString(string: str, attributes: attributes) 92 | let range = (str as NSString).range(of: "Pro") 93 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: UIColor(red: 90.0 / 255.0, green: 104.0 / 255.0, blue: 90.0 / 255.0, alpha: 1)], range: range) 94 | 95 | UIView.transition(with: self.iPhoneLabel, duration: 2, options: .transitionCrossDissolve, animations: { 96 | self.iPhoneLabel.attributedText = attStr 97 | }, completion: nil) 98 | 99 | UIView.animate(withDuration: 2, animations: { 100 | self.subLabel.alpha = 1 101 | }) 102 | }) 103 | 104 | playerView.player?.play() 105 | UIView.animate(withDuration: 1.0, animations: { 106 | self.playerView.alpha = 1 107 | }) 108 | } 109 | 110 | override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent } 111 | } 112 | 113 | // MARK:- 私有API 114 | private extension iPhone11ProViewController { 115 | func _setupUI() { 116 | view.backgroundColor = .black 117 | 118 | videoPath = iPhone11ProViewController.videoPath() 119 | createPlayerView(CGRect(x: 0, 120 | y: NavTopMargin, 121 | width: PortraitScreenWidth, 122 | height: PortraitScreenWidth * (9.0 / 16.0)), 123 | videoURL: URL(fileURLWithPath: videoPath)) 124 | 125 | bgImgView.alpha = 0 126 | playerView.alpha = 0 127 | iPhoneLabel.alpha = 0 128 | subLabel.alpha = 0 129 | 130 | view.addSubview(bgImgView) 131 | view.addSubview(playerView) 132 | view.addSubview(iPhoneLabel) 133 | view.addSubview(subLabel) 134 | } 135 | 136 | func _loopAnimation(_ isIdentity: Bool, _ delay: TimeInterval) { 137 | let transform1 = CGAffineTransform.identity 138 | let transform2 = CGAffineTransform(translationX: -50, y: 50).concatenating(CGAffineTransform(scaleX: 0.87, y: 0.87)) 139 | let fromTransform = isIdentity ? transform2 : transform1 140 | let toTransform = isIdentity ? transform1 : transform2 141 | bgImgView.transform = fromTransform 142 | UIView.animate(withDuration: 50, delay: delay, options: .curveLinear, animations: { 143 | self.bgImgView.transform = toTransform 144 | }, completion: { finish in 145 | guard finish else { return } 146 | self._loopAnimation(!isIdentity, 3.0) 147 | }) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /JPPictureInPictureDemo/iPhone11ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iPhone11ViewController.swift 3 | // JPPictureInPictureDemo 4 | // 5 | // Created by 周健平 on 2020/6/28. 6 | // 7 | 8 | import UIKit 9 | 10 | class iPhone11ViewController: PlayerViewController { 11 | 12 | class func videoPath() -> String { Bundle.main.path(forResource: "iphone-11", ofType: "mp4")! } 13 | 14 | fileprivate let shadowLayer: CALayer = CALayer() 15 | 16 | fileprivate var imagePaths: [UIImage] = [] 17 | fileprivate var _imageIndex: Int = 0 18 | 19 | private var _isDidAppear = false 20 | 21 | private let bgImgView: UIImageView = { 22 | let bgImgView = UIImageView(frame: UIScreen.main.bounds) 23 | bgImgView.contentMode = .scaleAspectFill 24 | bgImgView.backgroundColor = .white 25 | return bgImgView 26 | }() 27 | 28 | private let iPhoneLabel: UILabel = { 29 | let iPhoneLabel = UILabel() 30 | 31 | let str: String = "iPhone 11" 32 | 33 | let shadow = NSShadow() 34 | shadow.shadowBlurRadius = 3 35 | shadow.shadowColor = UIColor(white: 0, alpha: 0.3) 36 | shadow.shadowOffset = CGSize(width: 0, height: 1) 37 | let attributes: [NSAttributedString.Key: Any] = 38 | [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 40), 39 | NSAttributedString.Key.foregroundColor: UIColor.systemPink, 40 | NSAttributedString.Key.shadow: shadow] 41 | let attStr = NSMutableAttributedString(string: str, attributes: attributes) 42 | 43 | let color0 = UIColor(red: 198.0 / 255.0, green: 194.0 / 255.0, blue: 211.0 / 255.0, alpha: 1) 44 | let color1 = UIColor(red: 158.0 / 255.0, green: 219.0 / 255.0, blue: 194.0 / 255.0, alpha: 1) 45 | let color2 = UIColor(red: 253.0 / 255.0, green: 226.0 / 255.0, blue: 109.0 / 255.0, alpha: 1) 46 | let color3 = UIColor(red: 248.0 / 255.0, green: 244.0 / 255.0, blue: 235.0 / 255.0, alpha: 1) 47 | let color4 = UIColor(red: 22.0 / 255.0, green: 25.0 / 255.0, blue: 25.0 / 255.0, alpha: 1) 48 | let color5 = UIColor(red: 169.0 / 255.0, green: 0, blue: 36.0 / 255.0, alpha: 1) 49 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: color0], range: NSRange(location: 0, length: 1)) 50 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: color1], range: NSRange(location: 1, length: 1)) 51 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: color2], range: NSRange(location: 2, length: 1)) 52 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: color3], range: NSRange(location: 3, length: 1)) 53 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: color4], range: NSRange(location: 4, length: 1)) 54 | attStr.addAttributes([NSAttributedString.Key.foregroundColor: color5], range: NSRange(location: 5, length: 1)) 55 | 56 | iPhoneLabel.attributedText = attStr 57 | iPhoneLabel.textAlignment = .center 58 | iPhoneLabel.sizeToFit() 59 | return iPhoneLabel 60 | }() 61 | 62 | private let subLabel: UILabel = { 63 | let subLabel = UILabel() 64 | let shadow = NSShadow() 65 | shadow.shadowBlurRadius = 3 66 | shadow.shadowColor = UIColor(white: 0, alpha: 0.3) 67 | shadow.shadowOffset = CGSize(width: 0, height: 1) 68 | let attributes: [NSAttributedString.Key: Any] = 69 | [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15), 70 | NSAttributedString.Key.foregroundColor: UIColor.white, 71 | NSAttributedString.Key.shadow: shadow] 72 | let attStr = NSMutableAttributedString(string: "一切都刚刚好。", attributes: attributes) 73 | subLabel.attributedText = attStr 74 | subLabel.textAlignment = .center 75 | subLabel.sizeToFit() 76 | return subLabel 77 | }() 78 | 79 | override func viewDidLoad() { 80 | super.viewDidLoad() 81 | _setupUI() 82 | } 83 | 84 | override func viewWillAppear(_ animated: Bool) { 85 | super.viewWillAppear(animated) 86 | 87 | if _isDidAppear { return } 88 | 89 | DispatchQueue.global().async { 90 | let image0 = UIImage(contentsOfFile: Bundle.main.path(forResource: "iphone-11_0", ofType: "jpg")!)! 91 | let image1 = UIImage(contentsOfFile: Bundle.main.path(forResource: "iphone-11_1", ofType: "jpg")!)! 92 | let image2 = UIImage(contentsOfFile: Bundle.main.path(forResource: "iphone-11_2", ofType: "jpg")!)! 93 | 94 | self.imagePaths.append(image0) 95 | self.imagePaths.append(image1) 96 | self.imagePaths.append(image2) 97 | 98 | DispatchQueue.main.async { [weak self] in 99 | self?._loopAnimation(delay: 0) 100 | } 101 | } 102 | } 103 | 104 | override func viewDidAppear(_ animated: Bool) { 105 | super.viewDidAppear(animated) 106 | 107 | if _isDidAppear { return } 108 | _isDidAppear = true 109 | 110 | UIView.animate(withDuration: 1, delay: 1, options: [], animations: { 111 | self.iPhoneLabel.alpha = 1 112 | }, completion: { finish in 113 | guard finish else { return } 114 | UIView.animate(withDuration: 1, animations: { 115 | self.subLabel.alpha = 1 116 | }) 117 | }) 118 | 119 | playerView.player?.play() 120 | } 121 | 122 | override var preferredStatusBarStyle: UIStatusBarStyle { .darkContent } 123 | } 124 | 125 | // MARK:- 私有API 126 | private extension iPhone11ViewController { 127 | func _setupUI() { 128 | view.backgroundColor = .white 129 | 130 | let x: CGFloat = 16.0 131 | let w: CGFloat = PortraitScreenWidth - 2 * x 132 | let h: CGFloat = w * (9.0 / 16.0) 133 | let y: CGFloat = PortraitScreenHeight * 0.3 134 | videoPath = iPhone11ViewController.videoPath() 135 | createPlayerView(CGRect(x: x, y: y, width: w, height: h), 136 | videoURL: URL(fileURLWithPath: videoPath)) 137 | playerView.layer.cornerRadius = 10 138 | playerView.layer.masksToBounds = true 139 | 140 | shadowLayer.shadowPath = UIBezierPath(roundedRect: playerView.frame, cornerRadius: playerView.layer.cornerRadius).cgPath 141 | shadowLayer.shadowColor = UIColor.black.cgColor 142 | shadowLayer.shadowOpacity = 0.5 143 | shadowLayer.shadowRadius = 10 144 | shadowLayer.shadowOffset = CGSize(width: 0, height: 5) 145 | 146 | iPhoneLabel.frame.origin = CGPoint(x: (PortraitScreenWidth - iPhoneLabel.frame.width) * 0.5, y: NavTopMargin + 15) 147 | iPhoneLabel.alpha = 0 148 | 149 | subLabel.frame.origin = CGPoint(x: (PortraitScreenWidth - subLabel.frame.width) * 0.5, y: iPhoneLabel.frame.maxY + 15) 150 | subLabel.alpha = 0 151 | 152 | view.addSubview(bgImgView) 153 | view.layer.addSublayer(shadowLayer) 154 | view.addSubview(playerView) 155 | view.addSubview(iPhoneLabel) 156 | view.addSubview(subLabel) 157 | } 158 | 159 | func _loopAnimation(delay: TimeInterval) { 160 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay) { [weak self] in 161 | guard let self = self else { return } 162 | if self._imageIndex >= self.imagePaths.count { 163 | self._imageIndex = 0 164 | } 165 | let bgImage = self.imagePaths[self._imageIndex] 166 | UIView.transition(with: self.bgImgView, duration: 3.0, options: .transitionCrossDissolve, animations: { 167 | self.bgImgView.image = bgImage 168 | }) { finish in 169 | guard finish else { return } 170 | self._imageIndex += 1 171 | self._loopAnimation(delay: 10.0) 172 | } 173 | } 174 | } 175 | } 176 | 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JPPictureInPictureDemo 2 | 3 | ## PictureInPictureDemo of iOS14. 4 | 5 | [Detailed description in blog](https://juejin.im/post/5ef9bc7d5188252e3426c0d2) 6 | 7 | ## Author 8 | 9 | Email: zhoujianping24@hotmail.com, Blog: https://www.jianshu.com/u/2edfbadd451c 10 | --------------------------------------------------------------------------------