├── .gitignore ├── Common ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg └── VideoProvider.swift ├── PiPBugDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── PiPBugDemoTV.xcscheme └── xcuserdata │ └── chad.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── PiPBugDemoIOS ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── IOSViewController.swift ├── Info.plist └── SceneDelegate.swift ├── PiPBugDemoTV ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon - App Store.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── App Icon.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Top Shelf Image Wide.imageset │ │ │ └── Contents.json │ │ └── Top Shelf Image.imageset │ │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── TVViewController.swift ├── PipBugDemoMac ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── MacViewController.swift └── PipBugDemoMac.entitlements └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.*~ 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## User settings 9 | xcuserdata/ 10 | 11 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 12 | *.xcscmblueprint 13 | *.xccheckout 14 | 15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 16 | build/ 17 | DerivedData/ 18 | *.moved-aside 19 | *.pbxuser 20 | !default.pbxuser 21 | *.mode1v3 22 | !default.mode1v3 23 | *.mode2v3 24 | !default.mode2v3 25 | *.perspectivev3 26 | !default.perspectivev3 27 | 28 | ## Gcc Patch 29 | /*.gcno 30 | -------------------------------------------------------------------------------- /Common/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzychad/PiPBugDemo/716ec2ae42cf315288e7f0651e9c0e159a4b1ecc/Common/1.jpg -------------------------------------------------------------------------------- /Common/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzychad/PiPBugDemo/716ec2ae42cf315288e7f0651e9c0e159a4b1ecc/Common/2.jpg -------------------------------------------------------------------------------- /Common/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzychad/PiPBugDemo/716ec2ae42cf315288e7f0651e9c0e159a4b1ecc/Common/3.jpg -------------------------------------------------------------------------------- /Common/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzychad/PiPBugDemo/716ec2ae42cf315288e7f0651e9c0e159a4b1ecc/Common/4.jpg -------------------------------------------------------------------------------- /Common/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzychad/PiPBugDemo/716ec2ae42cf315288e7f0651e9c0e159a4b1ecc/Common/5.jpg -------------------------------------------------------------------------------- /Common/VideoProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoProvider.swift 3 | // PiPBugDemo 4 | // 5 | // Created by Chad Etzel on 11/10/21. 6 | // 7 | 8 | import Foundation 9 | import AVFoundation 10 | 11 | class VideoProvider: NSObject { 12 | 13 | var bufferDisplayLayer: AVSampleBufferDisplayLayer = AVSampleBufferDisplayLayer() 14 | 15 | private var timer: Timer! 16 | private var imageIndex: Int = 0 17 | private var filenames: [String] = ["1", "2", "3", "4", "5"] 18 | 19 | 20 | func start() { 21 | let timerBlock: ((Timer) -> Void) = { timer in 22 | guard let data = self.getFrameJPEGData() else { return } 23 | let frameBuffer = self.sampleBufferFromJPEGData(data) 24 | if let buffer = frameBuffer { 25 | self.bufferDisplayLayer.enqueue(buffer) 26 | } else { 27 | print("missing frame") 28 | } 29 | } 30 | 31 | timer = Timer(timeInterval: 0.3, repeats: true, block: timerBlock) 32 | RunLoop.main.add(timer, forMode: .default) 33 | } 34 | 35 | func stop() { 36 | if timer != nil { 37 | timer.invalidate() 38 | timer = nil 39 | } 40 | } 41 | 42 | func isRunning() -> Bool { 43 | return timer != nil 44 | } 45 | 46 | func getFrameJPEGData() -> Data? { 47 | 48 | let filename = filenames[imageIndex] 49 | imageIndex = (imageIndex + 1) % filenames.count 50 | 51 | if let path = Bundle.main.path(forResource: filename, ofType: "jpg") { 52 | if let jpegData = try? Data(contentsOf: URL(fileURLWithPath: path)) { 53 | return jpegData 54 | } 55 | return nil 56 | } 57 | return nil 58 | } 59 | 60 | 61 | func sampleBufferFromJPEGData(_ jpegData: Data) -> CMSampleBuffer? { 62 | 63 | let rawPixelSize = CGSize(width: 640, height: 480) 64 | var format: CMFormatDescription? = nil 65 | let _ = CMVideoFormatDescriptionCreate(allocator: kCFAllocatorDefault, codecType: kCMVideoCodecType_JPEG, width: Int32(rawPixelSize.width), height: Int32(rawPixelSize.height), extensions: nil, formatDescriptionOut: &format) 66 | 67 | do { 68 | let cmBlockBuffer = try jpegData.toCMBlockBuffer() 69 | 70 | var size = jpegData.count 71 | 72 | var sampleBuffer: CMSampleBuffer? = nil 73 | let nowTime = CMTime(seconds: CACurrentMediaTime(), preferredTimescale: 60) 74 | let _1_60_s = CMTime(value: 1, timescale: 60) //CMTime(seconds: 1.0, preferredTimescale: 30) 75 | var timingInfo: CMSampleTimingInfo = CMSampleTimingInfo(duration: _1_60_s, presentationTimeStamp: nowTime, decodeTimeStamp: .invalid) 76 | 77 | let _ = CMSampleBufferCreateReady(allocator: kCFAllocatorDefault, dataBuffer: cmBlockBuffer, formatDescription: format, sampleCount: 1, sampleTimingEntryCount: 1, sampleTimingArray: &timingInfo, sampleSizeEntryCount: 1, sampleSizeArray: &size, sampleBufferOut: &sampleBuffer) 78 | if sampleBuffer != nil { 79 | //print("sending buffer to displayBufferLayer") 80 | //self.bufferDisplayLayer.enqueue(sampleBuffer!) 81 | return sampleBuffer 82 | } else { 83 | print("sampleBuffer is nil") 84 | return nil 85 | } 86 | } catch { 87 | print("error ugh ", error) 88 | return nil 89 | } 90 | } 91 | 92 | } 93 | 94 | 95 | private func freeBlock(_ refCon: UnsafeMutableRawPointer?, doomedMemoryBlock: UnsafeMutableRawPointer, sizeInBytes: Int) -> Void { 96 | let unmanagedData = Unmanaged.fromOpaque(refCon!) 97 | unmanagedData.release() 98 | } 99 | 100 | enum CMEncodingError: Error { 101 | case cmBlockCreationFailed 102 | } 103 | 104 | extension Data { 105 | 106 | func toCMBlockBuffer() throws -> CMBlockBuffer { 107 | // This block source is a manually retained pointer to our data instance. 108 | // The passed FreeBlock function manually releases it when the block buffer gets deallocated. 109 | let data = NSMutableData(data: self) 110 | var source = CMBlockBufferCustomBlockSource() 111 | source.refCon = Unmanaged.passRetained(data).toOpaque() 112 | source.FreeBlock = freeBlock 113 | 114 | var blockBuffer: CMBlockBuffer? 115 | let result = CMBlockBufferCreateWithMemoryBlock( 116 | allocator: kCFAllocatorDefault, 117 | memoryBlock: data.mutableBytes, 118 | blockLength: data.length, 119 | blockAllocator: kCFAllocatorNull, 120 | customBlockSource: &source, 121 | offsetToData: 0, 122 | dataLength: data.length, 123 | flags: 0, 124 | blockBufferOut: &blockBuffer) 125 | if OSStatus(result) != kCMBlockBufferNoErr { 126 | throw CMEncodingError.cmBlockCreationFailed 127 | } 128 | 129 | guard let buffer = blockBuffer else { 130 | throw CMEncodingError.cmBlockCreationFailed 131 | } 132 | 133 | assert(CMBlockBufferGetDataLength(buffer) == data.length) 134 | return buffer 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /PiPBugDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 63D3805E273C595500EB04B5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D3805D273C595500EB04B5 /* AppDelegate.swift */; }; 11 | 63D38060273C595500EB04B5 /* TVViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D3805F273C595500EB04B5 /* TVViewController.swift */; }; 12 | 63D38063273C595500EB04B5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63D38061273C595500EB04B5 /* Main.storyboard */; }; 13 | 63D38065273C595600EB04B5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63D38064273C595600EB04B5 /* Assets.xcassets */; }; 14 | 63D38068273C595600EB04B5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63D38066273C595600EB04B5 /* LaunchScreen.storyboard */; }; 15 | 63D38075273C599800EB04B5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D38074273C599800EB04B5 /* AppDelegate.swift */; }; 16 | 63D38077273C599800EB04B5 /* MacViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D38076273C599800EB04B5 /* MacViewController.swift */; }; 17 | 63D38079273C599900EB04B5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63D38078273C599900EB04B5 /* Assets.xcassets */; }; 18 | 63D3807C273C599900EB04B5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63D3807A273C599900EB04B5 /* Main.storyboard */; }; 19 | 63D38088273C5A1900EB04B5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D38087273C5A1900EB04B5 /* AppDelegate.swift */; }; 20 | 63D3808A273C5A1900EB04B5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D38089273C5A1900EB04B5 /* SceneDelegate.swift */; }; 21 | 63D3808C273C5A1900EB04B5 /* IOSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D3808B273C5A1900EB04B5 /* IOSViewController.swift */; }; 22 | 63D3808F273C5A1900EB04B5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63D3808D273C5A1900EB04B5 /* Main.storyboard */; }; 23 | 63D38091273C5A1A00EB04B5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63D38090273C5A1A00EB04B5 /* Assets.xcassets */; }; 24 | 63D38094273C5A1A00EB04B5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63D38092273C5A1A00EB04B5 /* LaunchScreen.storyboard */; }; 25 | 63D3809C273C5B4900EB04B5 /* VideoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D3809B273C5B4900EB04B5 /* VideoProvider.swift */; }; 26 | 63D3809D273C5B4900EB04B5 /* VideoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D3809B273C5B4900EB04B5 /* VideoProvider.swift */; }; 27 | 63D3809E273C5B4900EB04B5 /* VideoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63D3809B273C5B4900EB04B5 /* VideoProvider.swift */; }; 28 | 63D380A4273C5FA700EB04B5 /* 3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D3809F273C5FA700EB04B5 /* 3.jpg */; }; 29 | 63D380A5273C5FA700EB04B5 /* 3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D3809F273C5FA700EB04B5 /* 3.jpg */; }; 30 | 63D380A6273C5FA700EB04B5 /* 3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D3809F273C5FA700EB04B5 /* 3.jpg */; }; 31 | 63D380A7273C5FA700EB04B5 /* 5.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A0273C5FA700EB04B5 /* 5.jpg */; }; 32 | 63D380A8273C5FA700EB04B5 /* 5.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A0273C5FA700EB04B5 /* 5.jpg */; }; 33 | 63D380A9273C5FA700EB04B5 /* 5.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A0273C5FA700EB04B5 /* 5.jpg */; }; 34 | 63D380AA273C5FA700EB04B5 /* 4.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A1273C5FA700EB04B5 /* 4.jpg */; }; 35 | 63D380AB273C5FA700EB04B5 /* 4.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A1273C5FA700EB04B5 /* 4.jpg */; }; 36 | 63D380AC273C5FA700EB04B5 /* 4.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A1273C5FA700EB04B5 /* 4.jpg */; }; 37 | 63D380AD273C5FA700EB04B5 /* 1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A2273C5FA700EB04B5 /* 1.jpg */; }; 38 | 63D380AE273C5FA700EB04B5 /* 1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A2273C5FA700EB04B5 /* 1.jpg */; }; 39 | 63D380AF273C5FA700EB04B5 /* 1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A2273C5FA700EB04B5 /* 1.jpg */; }; 40 | 63D380B0273C5FA700EB04B5 /* 2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A3273C5FA700EB04B5 /* 2.jpg */; }; 41 | 63D380B1273C5FA700EB04B5 /* 2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A3273C5FA700EB04B5 /* 2.jpg */; }; 42 | 63D380B2273C5FA700EB04B5 /* 2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 63D380A3273C5FA700EB04B5 /* 2.jpg */; }; 43 | /* End PBXBuildFile section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 63D3805A273C595500EB04B5 /* PiPBugDemoTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PiPBugDemoTV.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 63D3805D273C595500EB04B5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 48 | 63D3805F273C595500EB04B5 /* TVViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVViewController.swift; sourceTree = ""; }; 49 | 63D38062273C595500EB04B5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | 63D38064273C595600EB04B5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | 63D38067273C595600EB04B5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | 63D38072273C599800EB04B5 /* PipBugDemoMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PipBugDemoMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 63D38074273C599800EB04B5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 54 | 63D38076273C599800EB04B5 /* MacViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacViewController.swift; sourceTree = ""; }; 55 | 63D38078273C599900EB04B5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 63D3807B273C599900EB04B5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 63D3807D273C599900EB04B5 /* PipBugDemoMac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PipBugDemoMac.entitlements; sourceTree = ""; }; 58 | 63D38085273C5A1900EB04B5 /* PiPBugDemoIOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PiPBugDemoIOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 63D38087273C5A1900EB04B5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 60 | 63D38089273C5A1900EB04B5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 61 | 63D3808B273C5A1900EB04B5 /* IOSViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOSViewController.swift; sourceTree = ""; }; 62 | 63D3808E273C5A1900EB04B5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63 | 63D38090273C5A1A00EB04B5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64 | 63D38093273C5A1A00EB04B5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 65 | 63D38095273C5A1A00EB04B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 63D38099273C5A2D00EB04B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 67 | 63D3809B273C5B4900EB04B5 /* VideoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoProvider.swift; sourceTree = ""; }; 68 | 63D3809F273C5FA700EB04B5 /* 3.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 3.jpg; sourceTree = ""; }; 69 | 63D380A0273C5FA700EB04B5 /* 5.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 5.jpg; sourceTree = ""; }; 70 | 63D380A1273C5FA700EB04B5 /* 4.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 4.jpg; sourceTree = ""; }; 71 | 63D380A2273C5FA700EB04B5 /* 1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 1.jpg; sourceTree = ""; }; 72 | 63D380A3273C5FA700EB04B5 /* 2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 2.jpg; sourceTree = ""; }; 73 | 63D380B3273C7E9800EB04B5 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | 63D38057273C595500EB04B5 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | 63D3806F273C599800EB04B5 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | 63D38082273C5A1900EB04B5 /* Frameworks */ = { 92 | isa = PBXFrameworksBuildPhase; 93 | buildActionMask = 2147483647; 94 | files = ( 95 | ); 96 | runOnlyForDeploymentPostprocessing = 0; 97 | }; 98 | /* End PBXFrameworksBuildPhase section */ 99 | 100 | /* Begin PBXGroup section */ 101 | 63D38051273C595500EB04B5 = { 102 | isa = PBXGroup; 103 | children = ( 104 | 63D380B3273C7E9800EB04B5 /* README.md */, 105 | 63D3809A273C5B1700EB04B5 /* Common */, 106 | 63D3805C273C595500EB04B5 /* PiPBugDemoTV */, 107 | 63D38073273C599800EB04B5 /* PipBugDemoMac */, 108 | 63D38086273C5A1900EB04B5 /* PiPBugDemoIOS */, 109 | 63D3805B273C595500EB04B5 /* Products */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | 63D3805B273C595500EB04B5 /* Products */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 63D3805A273C595500EB04B5 /* PiPBugDemoTV.app */, 117 | 63D38072273C599800EB04B5 /* PipBugDemoMac.app */, 118 | 63D38085273C5A1900EB04B5 /* PiPBugDemoIOS.app */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | 63D3805C273C595500EB04B5 /* PiPBugDemoTV */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 63D38099273C5A2D00EB04B5 /* Info.plist */, 127 | 63D3805D273C595500EB04B5 /* AppDelegate.swift */, 128 | 63D3805F273C595500EB04B5 /* TVViewController.swift */, 129 | 63D38061273C595500EB04B5 /* Main.storyboard */, 130 | 63D38064273C595600EB04B5 /* Assets.xcassets */, 131 | 63D38066273C595600EB04B5 /* LaunchScreen.storyboard */, 132 | ); 133 | path = PiPBugDemoTV; 134 | sourceTree = ""; 135 | }; 136 | 63D38073273C599800EB04B5 /* PipBugDemoMac */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 63D38074273C599800EB04B5 /* AppDelegate.swift */, 140 | 63D38076273C599800EB04B5 /* MacViewController.swift */, 141 | 63D38078273C599900EB04B5 /* Assets.xcassets */, 142 | 63D3807A273C599900EB04B5 /* Main.storyboard */, 143 | 63D3807D273C599900EB04B5 /* PipBugDemoMac.entitlements */, 144 | ); 145 | path = PipBugDemoMac; 146 | sourceTree = ""; 147 | }; 148 | 63D38086273C5A1900EB04B5 /* PiPBugDemoIOS */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 63D38087273C5A1900EB04B5 /* AppDelegate.swift */, 152 | 63D38089273C5A1900EB04B5 /* SceneDelegate.swift */, 153 | 63D3808B273C5A1900EB04B5 /* IOSViewController.swift */, 154 | 63D3808D273C5A1900EB04B5 /* Main.storyboard */, 155 | 63D38090273C5A1A00EB04B5 /* Assets.xcassets */, 156 | 63D38092273C5A1A00EB04B5 /* LaunchScreen.storyboard */, 157 | 63D38095273C5A1A00EB04B5 /* Info.plist */, 158 | ); 159 | path = PiPBugDemoIOS; 160 | sourceTree = ""; 161 | }; 162 | 63D3809A273C5B1700EB04B5 /* Common */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 63D3809B273C5B4900EB04B5 /* VideoProvider.swift */, 166 | 63D380A2273C5FA700EB04B5 /* 1.jpg */, 167 | 63D380A3273C5FA700EB04B5 /* 2.jpg */, 168 | 63D3809F273C5FA700EB04B5 /* 3.jpg */, 169 | 63D380A1273C5FA700EB04B5 /* 4.jpg */, 170 | 63D380A0273C5FA700EB04B5 /* 5.jpg */, 171 | ); 172 | path = Common; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXGroup section */ 176 | 177 | /* Begin PBXNativeTarget section */ 178 | 63D38059273C595500EB04B5 /* PiPBugDemoTV */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = 63D3806B273C595600EB04B5 /* Build configuration list for PBXNativeTarget "PiPBugDemoTV" */; 181 | buildPhases = ( 182 | 63D38056273C595500EB04B5 /* Sources */, 183 | 63D38057273C595500EB04B5 /* Frameworks */, 184 | 63D38058273C595500EB04B5 /* Resources */, 185 | ); 186 | buildRules = ( 187 | ); 188 | dependencies = ( 189 | ); 190 | name = PiPBugDemoTV; 191 | productName = PiPBugDemo; 192 | productReference = 63D3805A273C595500EB04B5 /* PiPBugDemoTV.app */; 193 | productType = "com.apple.product-type.application"; 194 | }; 195 | 63D38071273C599800EB04B5 /* PipBugDemoMac */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = 63D3807E273C599900EB04B5 /* Build configuration list for PBXNativeTarget "PipBugDemoMac" */; 198 | buildPhases = ( 199 | 63D3806E273C599800EB04B5 /* Sources */, 200 | 63D3806F273C599800EB04B5 /* Frameworks */, 201 | 63D38070273C599800EB04B5 /* Resources */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | ); 207 | name = PipBugDemoMac; 208 | productName = PipBugDemoMac; 209 | productReference = 63D38072273C599800EB04B5 /* PipBugDemoMac.app */; 210 | productType = "com.apple.product-type.application"; 211 | }; 212 | 63D38084273C5A1900EB04B5 /* PiPBugDemoIOS */ = { 213 | isa = PBXNativeTarget; 214 | buildConfigurationList = 63D38096273C5A1A00EB04B5 /* Build configuration list for PBXNativeTarget "PiPBugDemoIOS" */; 215 | buildPhases = ( 216 | 63D38081273C5A1900EB04B5 /* Sources */, 217 | 63D38082273C5A1900EB04B5 /* Frameworks */, 218 | 63D38083273C5A1900EB04B5 /* Resources */, 219 | ); 220 | buildRules = ( 221 | ); 222 | dependencies = ( 223 | ); 224 | name = PiPBugDemoIOS; 225 | productName = PiPBugDemoIOS; 226 | productReference = 63D38085273C5A1900EB04B5 /* PiPBugDemoIOS.app */; 227 | productType = "com.apple.product-type.application"; 228 | }; 229 | /* End PBXNativeTarget section */ 230 | 231 | /* Begin PBXProject section */ 232 | 63D38052273C595500EB04B5 /* Project object */ = { 233 | isa = PBXProject; 234 | attributes = { 235 | BuildIndependentTargetsInParallel = 1; 236 | LastSwiftUpdateCheck = 1310; 237 | LastUpgradeCheck = 1310; 238 | TargetAttributes = { 239 | 63D38059273C595500EB04B5 = { 240 | CreatedOnToolsVersion = 13.1; 241 | }; 242 | 63D38071273C599800EB04B5 = { 243 | CreatedOnToolsVersion = 13.1; 244 | }; 245 | 63D38084273C5A1900EB04B5 = { 246 | CreatedOnToolsVersion = 13.1; 247 | }; 248 | }; 249 | }; 250 | buildConfigurationList = 63D38055273C595500EB04B5 /* Build configuration list for PBXProject "PiPBugDemo" */; 251 | compatibilityVersion = "Xcode 13.0"; 252 | developmentRegion = en; 253 | hasScannedForEncodings = 0; 254 | knownRegions = ( 255 | en, 256 | Base, 257 | ); 258 | mainGroup = 63D38051273C595500EB04B5; 259 | productRefGroup = 63D3805B273C595500EB04B5 /* Products */; 260 | projectDirPath = ""; 261 | projectRoot = ""; 262 | targets = ( 263 | 63D38059273C595500EB04B5 /* PiPBugDemoTV */, 264 | 63D38071273C599800EB04B5 /* PipBugDemoMac */, 265 | 63D38084273C5A1900EB04B5 /* PiPBugDemoIOS */, 266 | ); 267 | }; 268 | /* End PBXProject section */ 269 | 270 | /* Begin PBXResourcesBuildPhase section */ 271 | 63D38058273C595500EB04B5 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 63D380AA273C5FA700EB04B5 /* 4.jpg in Resources */, 276 | 63D38068273C595600EB04B5 /* LaunchScreen.storyboard in Resources */, 277 | 63D380A7273C5FA700EB04B5 /* 5.jpg in Resources */, 278 | 63D38065273C595600EB04B5 /* Assets.xcassets in Resources */, 279 | 63D38063273C595500EB04B5 /* Main.storyboard in Resources */, 280 | 63D380A4273C5FA700EB04B5 /* 3.jpg in Resources */, 281 | 63D380B0273C5FA700EB04B5 /* 2.jpg in Resources */, 282 | 63D380AD273C5FA700EB04B5 /* 1.jpg in Resources */, 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | 63D38070273C599800EB04B5 /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 63D380A5273C5FA700EB04B5 /* 3.jpg in Resources */, 291 | 63D380A8273C5FA700EB04B5 /* 5.jpg in Resources */, 292 | 63D380B1273C5FA700EB04B5 /* 2.jpg in Resources */, 293 | 63D38079273C599900EB04B5 /* Assets.xcassets in Resources */, 294 | 63D380AE273C5FA700EB04B5 /* 1.jpg in Resources */, 295 | 63D380AB273C5FA700EB04B5 /* 4.jpg in Resources */, 296 | 63D3807C273C599900EB04B5 /* Main.storyboard in Resources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | 63D38083273C5A1900EB04B5 /* Resources */ = { 301 | isa = PBXResourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | 63D380AC273C5FA700EB04B5 /* 4.jpg in Resources */, 305 | 63D38094273C5A1A00EB04B5 /* LaunchScreen.storyboard in Resources */, 306 | 63D380A9273C5FA700EB04B5 /* 5.jpg in Resources */, 307 | 63D38091273C5A1A00EB04B5 /* Assets.xcassets in Resources */, 308 | 63D3808F273C5A1900EB04B5 /* Main.storyboard in Resources */, 309 | 63D380A6273C5FA700EB04B5 /* 3.jpg in Resources */, 310 | 63D380B2273C5FA700EB04B5 /* 2.jpg in Resources */, 311 | 63D380AF273C5FA700EB04B5 /* 1.jpg in Resources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | /* End PBXResourcesBuildPhase section */ 316 | 317 | /* Begin PBXSourcesBuildPhase section */ 318 | 63D38056273C595500EB04B5 /* Sources */ = { 319 | isa = PBXSourcesBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | 63D3809C273C5B4900EB04B5 /* VideoProvider.swift in Sources */, 323 | 63D38060273C595500EB04B5 /* TVViewController.swift in Sources */, 324 | 63D3805E273C595500EB04B5 /* AppDelegate.swift in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | 63D3806E273C599800EB04B5 /* Sources */ = { 329 | isa = PBXSourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | 63D3809D273C5B4900EB04B5 /* VideoProvider.swift in Sources */, 333 | 63D38077273C599800EB04B5 /* MacViewController.swift in Sources */, 334 | 63D38075273C599800EB04B5 /* AppDelegate.swift in Sources */, 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | 63D38081273C5A1900EB04B5 /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | 63D3809E273C5B4900EB04B5 /* VideoProvider.swift in Sources */, 343 | 63D3808C273C5A1900EB04B5 /* IOSViewController.swift in Sources */, 344 | 63D38088273C5A1900EB04B5 /* AppDelegate.swift in Sources */, 345 | 63D3808A273C5A1900EB04B5 /* SceneDelegate.swift in Sources */, 346 | ); 347 | runOnlyForDeploymentPostprocessing = 0; 348 | }; 349 | /* End PBXSourcesBuildPhase section */ 350 | 351 | /* Begin PBXVariantGroup section */ 352 | 63D38061273C595500EB04B5 /* Main.storyboard */ = { 353 | isa = PBXVariantGroup; 354 | children = ( 355 | 63D38062273C595500EB04B5 /* Base */, 356 | ); 357 | name = Main.storyboard; 358 | sourceTree = ""; 359 | }; 360 | 63D38066273C595600EB04B5 /* LaunchScreen.storyboard */ = { 361 | isa = PBXVariantGroup; 362 | children = ( 363 | 63D38067273C595600EB04B5 /* Base */, 364 | ); 365 | name = LaunchScreen.storyboard; 366 | sourceTree = ""; 367 | }; 368 | 63D3807A273C599900EB04B5 /* Main.storyboard */ = { 369 | isa = PBXVariantGroup; 370 | children = ( 371 | 63D3807B273C599900EB04B5 /* Base */, 372 | ); 373 | name = Main.storyboard; 374 | sourceTree = ""; 375 | }; 376 | 63D3808D273C5A1900EB04B5 /* Main.storyboard */ = { 377 | isa = PBXVariantGroup; 378 | children = ( 379 | 63D3808E273C5A1900EB04B5 /* Base */, 380 | ); 381 | name = Main.storyboard; 382 | sourceTree = ""; 383 | }; 384 | 63D38092273C5A1A00EB04B5 /* LaunchScreen.storyboard */ = { 385 | isa = PBXVariantGroup; 386 | children = ( 387 | 63D38093273C5A1A00EB04B5 /* Base */, 388 | ); 389 | name = LaunchScreen.storyboard; 390 | sourceTree = ""; 391 | }; 392 | /* End PBXVariantGroup section */ 393 | 394 | /* Begin XCBuildConfiguration section */ 395 | 63D38069273C595600EB04B5 /* Debug */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | CLANG_ANALYZER_NONNULL = YES; 400 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 401 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 402 | CLANG_CXX_LIBRARY = "libc++"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_ENABLE_OBJC_WEAK = YES; 406 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_COMMA = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 413 | CLANG_WARN_EMPTY_BODY = YES; 414 | CLANG_WARN_ENUM_CONVERSION = YES; 415 | CLANG_WARN_INFINITE_RECURSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_STRICT_PROTOTYPES = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 425 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = dwarf; 430 | ENABLE_STRICT_OBJC_MSGSEND = YES; 431 | ENABLE_TESTABILITY = YES; 432 | GCC_C_LANGUAGE_STANDARD = gnu11; 433 | GCC_DYNAMIC_NO_PIC = NO; 434 | GCC_NO_COMMON_BLOCKS = YES; 435 | GCC_OPTIMIZATION_LEVEL = 0; 436 | GCC_PREPROCESSOR_DEFINITIONS = ( 437 | "DEBUG=1", 438 | "$(inherited)", 439 | ); 440 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 441 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 442 | GCC_WARN_UNDECLARED_SELECTOR = YES; 443 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 444 | GCC_WARN_UNUSED_FUNCTION = YES; 445 | GCC_WARN_UNUSED_VARIABLE = YES; 446 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 447 | MTL_FAST_MATH = YES; 448 | ONLY_ACTIVE_ARCH = YES; 449 | SDKROOT = appletvos; 450 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 451 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 452 | TVOS_DEPLOYMENT_TARGET = 15.0; 453 | }; 454 | name = Debug; 455 | }; 456 | 63D3806A273C595600EB04B5 /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | ALWAYS_SEARCH_USER_PATHS = NO; 460 | CLANG_ANALYZER_NONNULL = YES; 461 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 462 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 463 | CLANG_CXX_LIBRARY = "libc++"; 464 | CLANG_ENABLE_MODULES = YES; 465 | CLANG_ENABLE_OBJC_ARC = YES; 466 | CLANG_ENABLE_OBJC_WEAK = YES; 467 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 468 | CLANG_WARN_BOOL_CONVERSION = YES; 469 | CLANG_WARN_COMMA = YES; 470 | CLANG_WARN_CONSTANT_CONVERSION = YES; 471 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 472 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 473 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 474 | CLANG_WARN_EMPTY_BODY = YES; 475 | CLANG_WARN_ENUM_CONVERSION = YES; 476 | CLANG_WARN_INFINITE_RECURSION = YES; 477 | CLANG_WARN_INT_CONVERSION = YES; 478 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 479 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 480 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 481 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 482 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 483 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 484 | CLANG_WARN_STRICT_PROTOTYPES = YES; 485 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 486 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 487 | CLANG_WARN_UNREACHABLE_CODE = YES; 488 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 489 | COPY_PHASE_STRIP = NO; 490 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 491 | ENABLE_NS_ASSERTIONS = NO; 492 | ENABLE_STRICT_OBJC_MSGSEND = YES; 493 | GCC_C_LANGUAGE_STANDARD = gnu11; 494 | GCC_NO_COMMON_BLOCKS = YES; 495 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 496 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 497 | GCC_WARN_UNDECLARED_SELECTOR = YES; 498 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 499 | GCC_WARN_UNUSED_FUNCTION = YES; 500 | GCC_WARN_UNUSED_VARIABLE = YES; 501 | MTL_ENABLE_DEBUG_INFO = NO; 502 | MTL_FAST_MATH = YES; 503 | SDKROOT = appletvos; 504 | SWIFT_COMPILATION_MODE = wholemodule; 505 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 506 | TVOS_DEPLOYMENT_TARGET = 15.0; 507 | VALIDATE_PRODUCT = YES; 508 | }; 509 | name = Release; 510 | }; 511 | 63D3806C273C595600EB04B5 /* Debug */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 515 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 516 | CODE_SIGN_STYLE = Automatic; 517 | CURRENT_PROJECT_VERSION = 1; 518 | DEVELOPMENT_TEAM = ""; 519 | GENERATE_INFOPLIST_FILE = YES; 520 | INFOPLIST_FILE = PiPBugDemoTV/Info.plist; 521 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 522 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 523 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; 524 | LD_RUNPATH_SEARCH_PATHS = ( 525 | "$(inherited)", 526 | "@executable_path/Frameworks", 527 | ); 528 | MARKETING_VERSION = 1.0; 529 | PRODUCT_BUNDLE_IDENTIFIER = com.example.PiPBugDemoTV; 530 | PRODUCT_NAME = PiPBugDemoTV; 531 | SWIFT_EMIT_LOC_STRINGS = YES; 532 | SWIFT_VERSION = 5.0; 533 | TARGETED_DEVICE_FAMILY = 3; 534 | }; 535 | name = Debug; 536 | }; 537 | 63D3806D273C595600EB04B5 /* Release */ = { 538 | isa = XCBuildConfiguration; 539 | buildSettings = { 540 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 541 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 542 | CODE_SIGN_STYLE = Automatic; 543 | CURRENT_PROJECT_VERSION = 1; 544 | DEVELOPMENT_TEAM = ""; 545 | GENERATE_INFOPLIST_FILE = YES; 546 | INFOPLIST_FILE = PiPBugDemoTV/Info.plist; 547 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 548 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 549 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic; 550 | LD_RUNPATH_SEARCH_PATHS = ( 551 | "$(inherited)", 552 | "@executable_path/Frameworks", 553 | ); 554 | MARKETING_VERSION = 1.0; 555 | PRODUCT_BUNDLE_IDENTIFIER = com.example.PiPBugDemoTV; 556 | PRODUCT_NAME = PiPBugDemoTV; 557 | SWIFT_EMIT_LOC_STRINGS = YES; 558 | SWIFT_VERSION = 5.0; 559 | TARGETED_DEVICE_FAMILY = 3; 560 | }; 561 | name = Release; 562 | }; 563 | 63D3807F273C599900EB04B5 /* Debug */ = { 564 | isa = XCBuildConfiguration; 565 | buildSettings = { 566 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 567 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 568 | CODE_SIGN_ENTITLEMENTS = PipBugDemoMac/PipBugDemoMac.entitlements; 569 | CODE_SIGN_STYLE = Automatic; 570 | COMBINE_HIDPI_IMAGES = YES; 571 | CURRENT_PROJECT_VERSION = 1; 572 | DEVELOPMENT_TEAM = ""; 573 | ENABLE_HARDENED_RUNTIME = YES; 574 | GENERATE_INFOPLIST_FILE = YES; 575 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 576 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 577 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 578 | LD_RUNPATH_SEARCH_PATHS = ( 579 | "$(inherited)", 580 | "@executable_path/../Frameworks", 581 | ); 582 | MACOSX_DEPLOYMENT_TARGET = 12.0; 583 | MARKETING_VERSION = 1.0; 584 | PRODUCT_BUNDLE_IDENTIFIER = com.example.PipBugDemoMac; 585 | PRODUCT_NAME = "$(TARGET_NAME)"; 586 | SDKROOT = macosx; 587 | SWIFT_EMIT_LOC_STRINGS = YES; 588 | SWIFT_VERSION = 5.0; 589 | }; 590 | name = Debug; 591 | }; 592 | 63D38080273C599900EB04B5 /* Release */ = { 593 | isa = XCBuildConfiguration; 594 | buildSettings = { 595 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 596 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 597 | CODE_SIGN_ENTITLEMENTS = PipBugDemoMac/PipBugDemoMac.entitlements; 598 | CODE_SIGN_STYLE = Automatic; 599 | COMBINE_HIDPI_IMAGES = YES; 600 | CURRENT_PROJECT_VERSION = 1; 601 | DEVELOPMENT_TEAM = ""; 602 | ENABLE_HARDENED_RUNTIME = YES; 603 | GENERATE_INFOPLIST_FILE = YES; 604 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 605 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 606 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 607 | LD_RUNPATH_SEARCH_PATHS = ( 608 | "$(inherited)", 609 | "@executable_path/../Frameworks", 610 | ); 611 | MACOSX_DEPLOYMENT_TARGET = 12.0; 612 | MARKETING_VERSION = 1.0; 613 | PRODUCT_BUNDLE_IDENTIFIER = com.example.PipBugDemoMac; 614 | PRODUCT_NAME = "$(TARGET_NAME)"; 615 | SDKROOT = macosx; 616 | SWIFT_EMIT_LOC_STRINGS = YES; 617 | SWIFT_VERSION = 5.0; 618 | }; 619 | name = Release; 620 | }; 621 | 63D38097273C5A1A00EB04B5 /* Debug */ = { 622 | isa = XCBuildConfiguration; 623 | buildSettings = { 624 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 625 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 626 | CODE_SIGN_STYLE = Automatic; 627 | CURRENT_PROJECT_VERSION = 1; 628 | DEVELOPMENT_TEAM = ""; 629 | GENERATE_INFOPLIST_FILE = YES; 630 | INFOPLIST_FILE = PiPBugDemoIOS/Info.plist; 631 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 632 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 633 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 634 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 635 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 636 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 637 | LD_RUNPATH_SEARCH_PATHS = ( 638 | "$(inherited)", 639 | "@executable_path/Frameworks", 640 | ); 641 | MARKETING_VERSION = 1.0; 642 | PRODUCT_BUNDLE_IDENTIFIER = com.example.PiPBugDemoIOS; 643 | PRODUCT_NAME = "$(TARGET_NAME)"; 644 | SDKROOT = iphoneos; 645 | SWIFT_EMIT_LOC_STRINGS = YES; 646 | SWIFT_VERSION = 5.0; 647 | TARGETED_DEVICE_FAMILY = "1,2"; 648 | }; 649 | name = Debug; 650 | }; 651 | 63D38098273C5A1A00EB04B5 /* Release */ = { 652 | isa = XCBuildConfiguration; 653 | buildSettings = { 654 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 655 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 656 | CODE_SIGN_STYLE = Automatic; 657 | CURRENT_PROJECT_VERSION = 1; 658 | DEVELOPMENT_TEAM = ""; 659 | GENERATE_INFOPLIST_FILE = YES; 660 | INFOPLIST_FILE = PiPBugDemoIOS/Info.plist; 661 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 662 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 663 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 664 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 665 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 666 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 667 | LD_RUNPATH_SEARCH_PATHS = ( 668 | "$(inherited)", 669 | "@executable_path/Frameworks", 670 | ); 671 | MARKETING_VERSION = 1.0; 672 | PRODUCT_BUNDLE_IDENTIFIER = com.example.PiPBugDemoIOS; 673 | PRODUCT_NAME = "$(TARGET_NAME)"; 674 | SDKROOT = iphoneos; 675 | SWIFT_EMIT_LOC_STRINGS = YES; 676 | SWIFT_VERSION = 5.0; 677 | TARGETED_DEVICE_FAMILY = "1,2"; 678 | }; 679 | name = Release; 680 | }; 681 | /* End XCBuildConfiguration section */ 682 | 683 | /* Begin XCConfigurationList section */ 684 | 63D38055273C595500EB04B5 /* Build configuration list for PBXProject "PiPBugDemo" */ = { 685 | isa = XCConfigurationList; 686 | buildConfigurations = ( 687 | 63D38069273C595600EB04B5 /* Debug */, 688 | 63D3806A273C595600EB04B5 /* Release */, 689 | ); 690 | defaultConfigurationIsVisible = 0; 691 | defaultConfigurationName = Release; 692 | }; 693 | 63D3806B273C595600EB04B5 /* Build configuration list for PBXNativeTarget "PiPBugDemoTV" */ = { 694 | isa = XCConfigurationList; 695 | buildConfigurations = ( 696 | 63D3806C273C595600EB04B5 /* Debug */, 697 | 63D3806D273C595600EB04B5 /* Release */, 698 | ); 699 | defaultConfigurationIsVisible = 0; 700 | defaultConfigurationName = Release; 701 | }; 702 | 63D3807E273C599900EB04B5 /* Build configuration list for PBXNativeTarget "PipBugDemoMac" */ = { 703 | isa = XCConfigurationList; 704 | buildConfigurations = ( 705 | 63D3807F273C599900EB04B5 /* Debug */, 706 | 63D38080273C599900EB04B5 /* Release */, 707 | ); 708 | defaultConfigurationIsVisible = 0; 709 | defaultConfigurationName = Release; 710 | }; 711 | 63D38096273C5A1A00EB04B5 /* Build configuration list for PBXNativeTarget "PiPBugDemoIOS" */ = { 712 | isa = XCConfigurationList; 713 | buildConfigurations = ( 714 | 63D38097273C5A1A00EB04B5 /* Debug */, 715 | 63D38098273C5A1A00EB04B5 /* Release */, 716 | ); 717 | defaultConfigurationIsVisible = 0; 718 | defaultConfigurationName = Release; 719 | }; 720 | /* End XCConfigurationList section */ 721 | }; 722 | rootObject = 63D38052273C595500EB04B5 /* Project object */; 723 | } 724 | -------------------------------------------------------------------------------- /PiPBugDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PiPBugDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PiPBugDemo.xcodeproj/xcshareddata/xcschemes/PiPBugDemoTV.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /PiPBugDemo.xcodeproj/xcuserdata/chad.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PiPBugDemoIOS.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | PiPBugDemoTV.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | PipBugDemoMac.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 1 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | 63D38059273C595500EB04B5 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PiPBugDemoIOS 4 | // 5 | // Created by Chad Etzel on 11/10/21. 6 | // 7 | 8 | import UIKit 9 | import AVFoundation 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | 19 | do { 20 | try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback) 21 | } catch { 22 | print("Setting category to AVAudioSessionCategoryPlayback failed.") 23 | } 24 | 25 | return true 26 | } 27 | 28 | // MARK: UISceneSession Lifecycle 29 | 30 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 31 | // Called when a new scene session is being created. 32 | // Use this method to select a configuration to create the new scene with. 33 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 34 | } 35 | 36 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 37 | // Called when the user discards a scene session. 38 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 39 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 40 | } 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/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 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/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 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/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 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/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 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/IOSViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PiPBugDemoIOS 4 | // 5 | // Created by Chad Etzel on 11/10/21. 6 | // 7 | 8 | import UIKit 9 | import AVKit 10 | import AVFoundation 11 | 12 | class IOSViewController: UIViewController, AVPictureInPictureSampleBufferPlaybackDelegate, AVPictureInPictureControllerDelegate { 13 | 14 | @IBOutlet weak var videoContainerView: UIView! 15 | @IBOutlet weak var pipButton: UIButton! 16 | @IBOutlet weak var pipSupportedLabel: UILabel! 17 | @IBOutlet weak var pipPossibleLabel: UILabel! 18 | 19 | var pipController: AVPictureInPictureController! = nil 20 | var pipObservation: NSKeyValueObservation? 21 | 22 | private let videoProvider = VideoProvider() 23 | 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | // Do any additional setup after loading the view. 28 | 29 | 30 | } 31 | 32 | override func viewDidAppear(_ animated: Bool) { 33 | super.viewDidAppear(animated) 34 | 35 | let bufferDisplayLayer = videoProvider.bufferDisplayLayer 36 | bufferDisplayLayer.frame = videoContainerView.bounds 37 | bufferDisplayLayer.videoGravity = .resizeAspect 38 | videoContainerView.layer.addSublayer(bufferDisplayLayer) 39 | 40 | self.pipButton.setTitle("", for: .normal) 41 | self.pipButton.setImage(AVPictureInPictureController.pictureInPictureButtonStartImage, for: .normal) 42 | 43 | videoProvider.start() 44 | 45 | let contentSource = AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoProvider.bufferDisplayLayer, playbackDelegate: self) 46 | 47 | 48 | #warning("This code MUST BE RUN ON A REAL DEVICE. NOT SUPPORTED ON SIMULATOR") 49 | 50 | pipController = AVPictureInPictureController(contentSource: contentSource) 51 | pipController.delegate = self 52 | 53 | pipObservation = pipController.observe(\AVPictureInPictureController.isPictureInPicturePossible, 54 | options: [.initial, .new]) { [weak self] _, change in 55 | print("isPictureInPicturePossible: \(change.newValue ?? false)") 56 | DispatchQueue.main.async { 57 | self?.pipPossibleLabel.text = "\(change.newValue ?? false)" 58 | } 59 | } 60 | 61 | pipSupportedLabel.text = "\(AVPictureInPictureController.isPictureInPictureSupported())" 62 | } 63 | 64 | 65 | @IBAction func _pipButtonDidTap(_ sender: Any) { 66 | print("PIP supported??????? \(AVPictureInPictureController.isPictureInPictureSupported())") 67 | if pipController.isPictureInPicturePossible { 68 | pipController.startPictureInPicture() 69 | } else { 70 | print("NO PIP AVAILABLE.. trying anyway") 71 | pipController.startPictureInPicture() 72 | } 73 | } 74 | 75 | // MARK: - AVPictureInPictureSampleBufferPlaybackDelegate 76 | 77 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) { 78 | print("\(#function)") 79 | if playing { 80 | videoProvider.start() 81 | } else { 82 | videoProvider.stop() 83 | } 84 | } 85 | 86 | func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange { 87 | print("\(#function)") 88 | return CMTimeRange(start: .negativeInfinity, duration: .positiveInfinity) 89 | } 90 | 91 | func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool { 92 | print("\(#function)") 93 | return false 94 | } 95 | 96 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) { 97 | print("\(#function)") 98 | } 99 | 100 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) { 101 | print("\(#function)") 102 | completionHandler() 103 | } 104 | 105 | 106 | // MARK: - AVPictureInPictureControllerDelegate 107 | 108 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { 109 | print("\(#function)") 110 | print("pip error: \(error)") 111 | } 112 | 113 | func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 114 | print("\(#function)") 115 | } 116 | 117 | func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 118 | print("\(#function)") 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | UIBackgroundModes 25 | 26 | audio 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /PiPBugDemoIOS/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // PiPBugDemoIOS 4 | // 5 | // Created by Chad Etzel on 11/10/21. 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 | -------------------------------------------------------------------------------- /PiPBugDemoTV/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PiPBugDemo 4 | // 5 | // Created by Chad Etzel on 11/10/21. 6 | // 7 | 8 | import UIKit 9 | import AVFoundation 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | do { 21 | try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback) 22 | } catch { 23 | print("Setting category to AVAudioSessionCategoryPlayback failed.") 24 | } 25 | 26 | return true 27 | } 28 | 29 | func applicationWillResignActive(_ application: UIApplication) { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 32 | } 33 | 34 | func applicationDidEnterBackground(_ application: UIApplication) { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | } 37 | 38 | func applicationWillEnterForeground(_ application: UIApplication) { 39 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 40 | } 41 | 42 | func applicationDidBecomeActive(_ application: UIApplication) { 43 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 44 | } 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /PiPBugDemoTV/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 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.imagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.imagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.imagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "author" : "xcode", 14 | "version" : 1 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "App Icon - App Store.imagestack", 5 | "idiom" : "tv", 6 | "role" : "primary-app-icon", 7 | "size" : "1280x768" 8 | }, 9 | { 10 | "filename" : "App Icon.imagestack", 11 | "idiom" : "tv", 12 | "role" : "primary-app-icon", 13 | "size" : "400x240" 14 | }, 15 | { 16 | "filename" : "Top Shelf Image Wide.imageset", 17 | "idiom" : "tv", 18 | "role" : "top-shelf-image-wide", 19 | "size" : "2320x720" 20 | }, 21 | { 22 | "filename" : "Top Shelf Image.imageset", 23 | "idiom" : "tv", 24 | "role" : "top-shelf-image", 25 | "size" : "1920x720" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "author" : "xcode", 22 | "version" : 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "author" : "xcode", 22 | "version" : 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PiPBugDemoTV/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 | -------------------------------------------------------------------------------- /PiPBugDemoTV/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 | 38 | 39 | 40 | 41 | 47 | 53 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /PiPBugDemoTV/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIBackgroundModes 6 | 7 | audio 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PiPBugDemoTV/TVViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PiPBugDemo 4 | // 5 | // Created by Chad Etzel on 11/10/21. 6 | // 7 | 8 | import UIKit 9 | import AVKit 10 | import AVFoundation 11 | 12 | class TVViewController: UIViewController, AVPictureInPictureSampleBufferPlaybackDelegate, AVPictureInPictureControllerDelegate { 13 | 14 | @IBOutlet weak var videoContainerView: UIView! 15 | @IBOutlet weak var pipButton: UIButton! 16 | @IBOutlet weak var pipSupportedLabel: UILabel! 17 | @IBOutlet weak var pipPossibleLabel: UILabel! 18 | 19 | var pipController: AVPictureInPictureController! = nil 20 | var pipObservation: NSKeyValueObservation? 21 | 22 | private let videoProvider = VideoProvider() 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | // Do any additional setup after loading the view. 27 | } 28 | 29 | override func viewDidAppear(_ animated: Bool) { 30 | super.viewDidAppear(animated) 31 | 32 | let bufferDisplayLayer = videoProvider.bufferDisplayLayer 33 | bufferDisplayLayer.frame = videoContainerView.bounds 34 | bufferDisplayLayer.videoGravity = .resizeAspect 35 | videoContainerView.layer.addSublayer(bufferDisplayLayer) 36 | 37 | self.pipButton.setTitle("", for: .normal) 38 | self.pipButton.setImage(AVPictureInPictureController.pictureInPictureButtonStartImage, for: .normal) 39 | 40 | videoProvider.start() 41 | 42 | let contentSource = AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoProvider.bufferDisplayLayer, playbackDelegate: self) 43 | 44 | 45 | #warning("This code MUST BE RUN ON A REAL DEVICE. NOT SUPPORTED ON SIMULATOR") 46 | 47 | pipController = AVPictureInPictureController(contentSource: contentSource) 48 | pipController.delegate = self 49 | 50 | pipObservation = pipController.observe(\AVPictureInPictureController.isPictureInPicturePossible, 51 | options: [.initial, .new]) { [weak self] _, change in 52 | print("isPictureInPicturePossible: \(change.newValue ?? false)") 53 | DispatchQueue.main.async { 54 | self?.pipPossibleLabel.text = "\(change.newValue ?? false)" 55 | } 56 | } 57 | 58 | pipSupportedLabel.text = "\(AVPictureInPictureController.isPictureInPictureSupported())" 59 | } 60 | 61 | 62 | @IBAction func _pipButtonDidTap(_ sender: Any) { 63 | print("PIP supported??????? \(AVPictureInPictureController.isPictureInPictureSupported())") 64 | if pipController.isPictureInPicturePossible { 65 | pipController.startPictureInPicture() 66 | } else { 67 | print("NO PIP AVAILABLE.. trying anyway") 68 | pipController.startPictureInPicture() 69 | } 70 | } 71 | 72 | // MARK: - AVPictureInPictureSampleBufferPlaybackDelegate 73 | 74 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) { 75 | print("\(#function)") 76 | if playing { 77 | videoProvider.start() 78 | } else { 79 | videoProvider.stop() 80 | } 81 | } 82 | 83 | func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange { 84 | 85 | #warning("This delegate method is never called on tvOS, this feels like a bug") 86 | 87 | print("\(#function)") 88 | return CMTimeRange(start: .negativeInfinity, duration: .positiveInfinity) 89 | } 90 | 91 | func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool { 92 | 93 | #warning("This delegate method is never called on tvOS, this feels like a bug") 94 | 95 | print("\(#function)") 96 | return false 97 | } 98 | 99 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) { 100 | print("\(#function)") 101 | } 102 | 103 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) { 104 | print("\(#function)") 105 | completionHandler() 106 | } 107 | 108 | 109 | // MARK: - AVPictureInPictureControllerDelegate 110 | 111 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { 112 | print("\(#function)") 113 | print("pip error: \(error)") 114 | } 115 | 116 | func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 117 | print("\(#function)") 118 | } 119 | 120 | func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 121 | print("\(#function)") 122 | } 123 | 124 | } 125 | 126 | -------------------------------------------------------------------------------- /PipBugDemoMac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PipBugDemoMac 4 | // 5 | // Created by Chad Etzel on 11/10/21. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 25 | return true 26 | } 27 | 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /PipBugDemoMac/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 | -------------------------------------------------------------------------------- /PipBugDemoMac/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PipBugDemoMac/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PipBugDemoMac/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 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | Default 530 | 531 | 532 | 533 | 534 | 535 | 536 | Left to Right 537 | 538 | 539 | 540 | 541 | 542 | 543 | Right to Left 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | Default 555 | 556 | 557 | 558 | 559 | 560 | 561 | Left to Right 562 | 563 | 564 | 565 | 566 | 567 | 568 | Right to Left 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | -------------------------------------------------------------------------------- /PipBugDemoMac/MacViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PipBugDemoMac 4 | // 5 | // Created by Chad Etzel on 11/10/21. 6 | // 7 | 8 | import Cocoa 9 | import AVKit 10 | import AVFoundation 11 | 12 | class MacViewController: NSViewController, AVPictureInPictureSampleBufferPlaybackDelegate, AVPictureInPictureControllerDelegate { 13 | 14 | @IBOutlet weak var videoContainerView: NSView! 15 | @IBOutlet weak var pipButton: NSButton! 16 | @IBOutlet weak var pipSupportedLabel: NSTextField! 17 | @IBOutlet weak var pipPossibleLabel: NSTextField! 18 | 19 | var pipController: AVPictureInPictureController! = nil 20 | var pipObservation: NSKeyValueObservation? 21 | 22 | private let videoProvider = VideoProvider() 23 | 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | // Do any additional setup after loading the view. 29 | 30 | videoContainerView.wantsLayer = true 31 | 32 | let bufferDisplayLayer = videoProvider.bufferDisplayLayer 33 | bufferDisplayLayer.frame = videoContainerView.bounds 34 | bufferDisplayLayer.videoGravity = .resizeAspect 35 | videoContainerView.layer?.addSublayer(bufferDisplayLayer) 36 | 37 | self.pipButton.title = "" 38 | self.pipButton.image = AVPictureInPictureController.pictureInPictureButtonStartImage 39 | videoProvider.start() 40 | 41 | let contentSource = AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoProvider.bufferDisplayLayer, playbackDelegate: self) 42 | 43 | 44 | pipController = AVPictureInPictureController(contentSource: contentSource) 45 | pipController.delegate = self 46 | 47 | pipObservation = pipController.observe(\AVPictureInPictureController.isPictureInPicturePossible, 48 | options: [.initial, .new]) { [weak self] _, change in 49 | print("isPictureInPicturePossible: \(change.newValue ?? false)") 50 | 51 | DispatchQueue.main.async { 52 | self?.pipPossibleLabel.stringValue = "\(change.newValue ?? false)" 53 | } 54 | } 55 | 56 | self.pipSupportedLabel.stringValue = "\(AVPictureInPictureController.isPictureInPictureSupported())" 57 | } 58 | 59 | @IBAction func _pipButtonDidTap(_ sender: Any) { 60 | print("PIP supported??????? \(AVPictureInPictureController.isPictureInPictureSupported())") 61 | if pipController.isPictureInPicturePossible { 62 | pipController.startPictureInPicture() 63 | } else { 64 | print("NO PIP AVAILABLE.. trying anyway") 65 | pipController.startPictureInPicture() 66 | } 67 | } 68 | 69 | // MARK: - AVPictureInPictureSampleBufferPlaybackDelegate 70 | 71 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) { 72 | print("\(#function)") 73 | if playing { 74 | videoProvider.start() 75 | } else { 76 | videoProvider.stop() 77 | } 78 | } 79 | 80 | func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange { 81 | 82 | #warning("This delegate method is never called on macOS, this feels like a bug") 83 | 84 | print("\(#function)") 85 | return CMTimeRange(start: .negativeInfinity, duration: .positiveInfinity) 86 | } 87 | 88 | func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool { 89 | 90 | #warning("This delegate method is never called on macOS, this feels like a bug") 91 | 92 | print("\(#function)") 93 | return false 94 | } 95 | 96 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) { 97 | print("\(#function)") 98 | } 99 | 100 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) { 101 | print("\(#function)") 102 | completionHandler() 103 | } 104 | 105 | 106 | // MARK: - AVPictureInPictureControllerDelegate 107 | 108 | func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { 109 | print("\(#function)") 110 | print("pip error: \(error)") 111 | } 112 | 113 | func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 114 | print("\(#function)") 115 | } 116 | 117 | func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { 118 | print("\(#function)") 119 | } 120 | 121 | 122 | } 123 | 124 | -------------------------------------------------------------------------------- /PipBugDemoMac/PipBugDemoMac.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # PiP Bug Demo 3 | 4 | I believe there is an Apple-level framework issue when using `AVPictureInPictureController` with an `AVSampleBufferDisplayLayer` content source on `macOS` or `tvOS` 5 | 6 | This bug manifests itself with the following consequence: **It is not possible to start a PiP session on `tvOS` or `macOS` when using an `AVSampleBufferDisplayLayer` as a content source for `AVPictureInPictureController`.** 7 | 8 | Tested on following platforms: 9 | ``` 10 | macOS: 12.0.1 (21A559) 11 | iOS: 15.1 (19B74) 12 | tvOS: 15.1.1 (19J587) 13 | ``` 14 | 15 | Submitted to Apple as `FB9751461` 16 | 17 | --- 18 | 19 | ### PLEASE NOTE: THIS CODE MUST BE RUN ON REAL DEVICES. SIMULATORS DO NOT SUPPORT THIS NEW PIP API 20 | 21 | --- 22 | 23 | This project presents 3 apps, one for `iOS`, `tvOS`, and `macOS` - which are all setup to use Picture in Picture with AVSampleBufferDisplayLayer as the content source. This is new API available in iOS 15, tvOS 15, and macOS 12. 24 | 25 | For `iOS` and `tvOS` I have added the `Picture in Picture` background mode in Signing and Capabilities (Info.plist entry). There doesn't seem to be an equivelent on `macOS`. 26 | 27 | 28 | I have also set the `AVAudioSession` to `.playback` on `iOS` and `tvOS` which is required to support Picture in Picture. Again, there seems to be no equivelent on `macOS` but I could be wrong? 29 | 30 | 31 | ```swift 32 | do { 33 | try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback) 34 | } catch { 35 | print("Setting category to AVAudioSessionCategoryPlayback failed.") 36 | } 37 | ``` 38 | 39 | In the relevant apps' view controller code, the AVPictureInPictureController setup is as follows: 40 | 41 | ```swift 42 | let contentSource = AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoProvider.bufferDisplayLayer, playbackDelegate: self) 43 | 44 | 45 | pipController = AVPictureInPictureController(contentSource: contentSource) 46 | pipController.delegate = self 47 | ``` 48 | 49 | On `iOS`, as soon as I instantiate `AVPictureInPictureController(contentSource: contentSource)` I am receiving delegate callbacks to the `AVPictureInPictureSampleBufferPlaybackDelegate` methods: 50 | 51 | ``` 52 | IOSViewController.pictureInPictureControllerTimeRangeForPlayback(_:) 53 | IOSViewController.pictureInPictureControllerIsPlaybackPaused(_:) 54 | ``` 55 | 56 | which it seems necessary for the system to call in order to setup Picture in Picture playback. 57 | 58 | However, these methods **ARE NEVER CALLED** on `tvOS` or `macOS` 59 | 60 | As a result, `pipController.isPictureInPicturePossible` is `true` on iOS but is always `false` on tvOS and macOS. 61 | 62 | I suspect that somewhere in the guts of the AVKit/AVFoundation framework, these delegate methods are not being queried on `tvOS` nor `macOS` and so PiP setup is never completed and it will always fail. 63 | --------------------------------------------------------------------------------