├── StreamingApp ├── Assets.xcassets │ ├── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Info.plist ├── Views │ ├── ManifestVideoPlayerView.swift │ ├── SimpleVideoPlayerView.swift │ ├── UIKitVideoPlayerView.swift │ └── BufferVideoPlayerView.swift ├── Video.swift ├── StreamingApp.swift └── VideoPlayerVM.swift ├── StreamingApp.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── mpr.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── README.md ├── LICENSE └── .gitignore /StreamingApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StreamingApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StreamingApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StreamingApp/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 | -------------------------------------------------------------------------------- /StreamingApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIBackgroundModes 6 | 7 | audio 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /StreamingApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /StreamingApp.xcodeproj/xcuserdata/mpr.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | StreamingApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HLS Streaming with SwiftUI 2 | Basic implementation of VideoPlayer for SwiftUI to play remote media files using HTTP Live Streaming (HLS). Multiple views provide different implementations, ranging a simple player implementation to a more advanced setup with a view model that continiously monitors the playback buffer using AVPlayer's AVPlayerItem through use of Combine and Observers. More information about HLS streaming can be found on Apple's website https://developer.apple.com/streaming/. 3 | -------------------------------------------------------------------------------- /StreamingApp/Views/ManifestVideoPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ManifestVideoPlayerView.swift 3 | // StreamingApp 4 | // 5 | // Created by Moritz Philip Recke on 09.11.21. 6 | // 7 | 8 | import SwiftUI 9 | import AVKit 10 | 11 | struct ManifestVideoPlayerView: View { 12 | private let player = AVPlayer(url: URL(string: "https://d142uv38695ylm.cloudfront.net/videos/promo/allesneu.land-trailer.m3u8")!) 13 | 14 | var body: some View { 15 | VideoPlayer(player: player) 16 | .navigationTitle("HLS manifest") 17 | .onAppear { 18 | player.play() 19 | } 20 | .onDisappear { 21 | player.pause() 22 | } 23 | } 24 | } 25 | 26 | struct ManifestVideoPlayerView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | ManifestVideoPlayerView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /StreamingApp/Video.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Video.swift 3 | // StreamingApp 4 | // 5 | // Created by Moritz Philip Recke on 21.10.21 for createwithswift.com. 6 | // 7 | 8 | import Foundation 9 | import Network 10 | 11 | struct Video { 12 | let name: String 13 | let streams: [Stream] 14 | } 15 | 16 | struct Stream { 17 | let resolution: Resolution 18 | let streamURL: URL 19 | } 20 | 21 | enum Resolution: Int, Identifiable, Comparable, CaseIterable { 22 | case p360 = 0 23 | case p540 24 | case p720 25 | case p1080 26 | 27 | var id: Int { rawValue } 28 | 29 | var displayValue: String { 30 | switch self { 31 | case .p360: return "360p" 32 | case .p540: return "540p" 33 | case .p720: return "720p" 34 | case .p1080: return "1080p" 35 | } 36 | } 37 | 38 | static func ==(lhs: Resolution, rhs: Resolution) -> Bool { 39 | lhs.rawValue == rhs.rawValue 40 | } 41 | 42 | static func <(lhs: Resolution, rhs: Resolution) -> Bool { 43 | lhs.rawValue < rhs.rawValue 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Create with Swift 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /StreamingApp/Views/SimpleVideoPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleVideoPlayerView.swift 3 | // StreamingApp 4 | // 5 | // Created by Moritz Philip Recke on 09.11.21. 6 | // 7 | 8 | import SwiftUI 9 | import AVKit 10 | 11 | struct SimpleVideoPlayerView: View { 12 | private let player = AVPlayer(url: URL(string: "https://d142uv38695ylm.cloudfront.net/videos/promo/allesneu.land-promo-trailer-360p.m3u8")!) 13 | 14 | var body: some View { 15 | VideoPlayer(player: player) { 16 | VStack { 17 | Text("Overlay") 18 | .font(.subheadline) 19 | .foregroundColor(.white) 20 | .frame(maxWidth: .infinity, alignment: .leading) 21 | Spacer() 22 | } 23 | } 24 | .navigationTitle("Simple video player") 25 | .onAppear() { 26 | player.play() 27 | } 28 | .onDisappear { 29 | player.pause() 30 | } 31 | } 32 | } 33 | 34 | struct SimpleVideoPlayerView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | SimpleVideoPlayerView() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /StreamingApp/StreamingApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamingApp.swift 3 | // StreamingApp 4 | // 5 | // Created by Moritz Philip Recke on 21.10.21 for createwithswift.com. 6 | // 7 | 8 | import SwiftUI 9 | import AVKit 10 | 11 | @main 12 | struct StreamingApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | NavigationView { 16 | List { 17 | NavigationLink("Simple video player") { 18 | SimpleVideoPlayerView() 19 | } 20 | 21 | NavigationLink("Observing player's status") { 22 | BufferVideoPlayerView() 23 | } 24 | 25 | NavigationLink("HLS manifest") { 26 | ManifestVideoPlayerView() 27 | } 28 | 29 | NavigationLink(destination: { 30 | UIKitVideoPlayerView() 31 | }, label: { 32 | (Text("PiP ") + Text("UIViewControllerRepresentable").font(.caption)) 33 | }) 34 | } 35 | .navigationBarTitle("StreamingApp") 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /StreamingApp/Views/UIKitVideoPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKitVideoPlayerView.swift 3 | // StreamingApp 4 | // 5 | // Created by Marco Falanga on 11/11/21. 6 | // 7 | 8 | import SwiftUI 9 | import AVKit 10 | 11 | struct UIKitVideoPlayerView: View { 12 | @StateObject private var videoPlayerVM = VideoPlayerViewModel.default 13 | 14 | var body: some View { 15 | UIVideoPlayer(player: videoPlayerVM.player) 16 | .onAppear { 17 | setAudioSessionCategory(to: .playback) 18 | videoPlayerVM.player.play() 19 | } 20 | .onDisappear { 21 | videoPlayerVM.player.pause() 22 | setAudioSessionCategory(to: .ambient) 23 | } 24 | } 25 | 26 | func setAudioSessionCategory(to value: AVAudioSession.Category) { 27 | let audioSession = AVAudioSession.sharedInstance() 28 | do { 29 | try audioSession.setCategory(value) 30 | } catch { 31 | print("Setting category to AVAudioSessionCategoryPlayback failed.") 32 | } 33 | } 34 | } 35 | 36 | struct UIKitVideoPlayerView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | UIKitVideoPlayerView() 39 | } 40 | } 41 | 42 | struct UIVideoPlayer: UIViewControllerRepresentable { 43 | 44 | let player: AVPlayer 45 | 46 | func makeUIViewController(context: Context) -> AVPlayerViewController { 47 | let vc = AVPlayerViewController() 48 | vc.player = player 49 | vc.canStartPictureInPictureAutomaticallyFromInline = true 50 | return vc 51 | } 52 | 53 | func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) { 54 | uiViewController.player = player 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /StreamingApp/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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /StreamingApp/Views/BufferVideoPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoPlayerView.swift 3 | // VideoPlayerView 4 | // 5 | // Created by Moritz Philip Recke on 19.02.21 for createwithswift.com. 6 | // 7 | 8 | import SwiftUI 9 | import AVKit 10 | 11 | struct BufferVideoPlayerView: View { 12 | 13 | @StateObject private var videoPlayerVM = VideoPlayerViewModel.default 14 | 15 | @State private var showResolutions = false 16 | 17 | var body: some View { 18 | ZStack { 19 | VStack(alignment: .leading) { 20 | Button("Change resolution") { 21 | withAnimation { 22 | showResolutions.toggle() 23 | } 24 | } 25 | .font(Font.body.bold()) 26 | 27 | VideoPlayer(player: videoPlayerVM.player) { 28 | Text(videoPlayerVM.namePlusResolution) 29 | .font(.subheadline) 30 | .foregroundColor(.white) 31 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 32 | .padding(8) 33 | } 34 | .onAppear() { 35 | videoPlayerVM.player.play() 36 | } 37 | .onDisappear { 38 | videoPlayerVM.player.pause() 39 | } 40 | } 41 | .padding() 42 | 43 | if showResolutions { 44 | VStack(spacing: 20) { 45 | Spacer() 46 | ForEach(Resolution.allCases) { resolution in 47 | Button(resolution.displayValue, action: { 48 | withAnimation { 49 | videoPlayerVM.selectedResolution = resolution 50 | showResolutions.toggle() 51 | } 52 | }) 53 | } 54 | 55 | Button(action: { 56 | withAnimation { 57 | showResolutions.toggle() 58 | } 59 | }, label: { 60 | Image(systemName: "xmark.circle") 61 | .imageScale(.large) 62 | }) 63 | .padding(.top) 64 | 65 | Spacer() 66 | } 67 | .frame(maxWidth: .infinity) 68 | .background(.thinMaterial) 69 | .transition(.move(edge: .bottom)) 70 | } 71 | } 72 | .navigationBarTitle("Observing player's status") 73 | } 74 | } 75 | 76 | struct ContentView_Previews: PreviewProvider { 77 | static var previews: some View { 78 | BufferVideoPlayerView() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /StreamingApp/VideoPlayerVM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoPlayerVM.swift 3 | // StreamingApp 4 | // 5 | // Created by Moritz Philip Recke on 26.10.21 for createwithswift.com. 6 | // 7 | 8 | import Foundation 9 | import Combine 10 | import AVKit 11 | 12 | final class VideoPlayerViewModel: ObservableObject { 13 | @Published var selectedResolution: Resolution 14 | @Published private var shouldLowerResolution = false 15 | 16 | let player = AVPlayer() 17 | private let video: Video 18 | private var subscriptions: Set = [] 19 | private var timeObserverToken: Any? 20 | 21 | var name: String { video.name } 22 | var namePlusResolution: String { video.name + " at " + selectedResolution.displayValue } 23 | 24 | init(video: Video, initialResolution: Resolution) { 25 | self.video = video 26 | self.selectedResolution = initialResolution 27 | 28 | $shouldLowerResolution 29 | .dropFirst() 30 | .filter({ $0 == true }) 31 | .sink(receiveValue: { [weak self] _ in 32 | guard let self = self else { return } 33 | self.lowerResolutionIfPossible() 34 | }) 35 | .store(in: &subscriptions) 36 | 37 | $selectedResolution 38 | .sink(receiveValue: { [weak self] resolution in 39 | guard let self = self else { return } 40 | self.replaceItem(with: resolution) 41 | self.setObserver() 42 | }) 43 | .store(in: &subscriptions) 44 | } 45 | 46 | deinit { 47 | if let timeObserverToken = timeObserverToken { 48 | player.removeTimeObserver(timeObserverToken) 49 | } 50 | } 51 | 52 | private func setObserver() { 53 | if let timeObserverToken = timeObserverToken { 54 | player.removeTimeObserver(timeObserverToken) 55 | } 56 | 57 | timeObserverToken = player.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 600), queue: DispatchQueue.main, using: { [weak self] time in 58 | guard let self = self, 59 | let currentItem = self.player.currentItem else { return } 60 | 61 | guard currentItem.isPlaybackBufferFull == false else { 62 | self.shouldLowerResolution = false 63 | return 64 | } 65 | 66 | if currentItem.status == AVPlayerItem.Status.readyToPlay { 67 | self.shouldLowerResolution = (!currentItem.isPlaybackLikelyToKeepUp && !currentItem.isPlaybackBufferEmpty) 68 | } 69 | }) 70 | } 71 | 72 | private func lowerResolutionIfPossible() { 73 | guard let newResolution = Resolution(rawValue: selectedResolution.rawValue - 1) else { return } 74 | selectedResolution = newResolution 75 | } 76 | 77 | private func replaceItem(with newResolution: Resolution) { 78 | guard let stream = self.video.streams.first(where: { $0.resolution == newResolution }) else { return } 79 | let currentTime: CMTime 80 | if let currentItem = player.currentItem { 81 | currentTime = currentItem.currentTime() 82 | } else { 83 | currentTime = .zero 84 | } 85 | 86 | player.replaceCurrentItem(with: AVPlayerItem(url: stream.streamURL)) 87 | player.seek(to: currentTime, toleranceBefore: .zero, toleranceAfter: .zero) 88 | } 89 | } 90 | 91 | extension VideoPlayerViewModel { 92 | static var `default`: Self { 93 | .init(video: Video(name: "Promo Video", streams: [ 94 | Stream(resolution: .p360, streamURL: URL(string: "https://d142uv38695ylm.cloudfront.net/videos/promo/allesneu.land-promo-trailer-360p.m3u8")!), 95 | Stream(resolution: .p540, streamURL: URL(string: "https://d142uv38695ylm.cloudfront.net/videos/promo/allesneu.land-promo-trailer-540p.m3u8")!), 96 | Stream(resolution: .p720, streamURL: URL(string: "https://d142uv38695ylm.cloudfront.net/videos/promo/allesneu.land-promo-trailer-720p.m3u8")!), 97 | Stream(resolution: .p1080, streamURL: URL(string: "https://d142uv38695ylm.cloudfront.net/videos/promo/allesneu.land-promo-trailer-1080p.m3u8")!) 98 | ]), initialResolution: .p540) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /StreamingApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 422A0DCB273D7240008D83B3 /* UIKitVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422A0DCA273D7240008D83B3 /* UIKitVideoPlayerView.swift */; }; 11 | A909DA8E273ABF6A009530BF /* SimpleVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A909DA8D273ABF6A009530BF /* SimpleVideoPlayerView.swift */; }; 12 | A909DA91273AC08C009530BF /* ManifestVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A909DA90273AC08C009530BF /* ManifestVideoPlayerView.swift */; }; 13 | A98B930F2727FDDA005672FA /* VideoPlayerVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98B930E2727FDDA005672FA /* VideoPlayerVM.swift */; }; 14 | A98D7E6F27219C5700004BA1 /* StreamingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98D7E6E27219C5700004BA1 /* StreamingApp.swift */; }; 15 | A98D7E7127219C5700004BA1 /* BufferVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98D7E7027219C5700004BA1 /* BufferVideoPlayerView.swift */; }; 16 | A98D7E7327219C5900004BA1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A98D7E7227219C5900004BA1 /* Assets.xcassets */; }; 17 | A98D7E7627219C5900004BA1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A98D7E7527219C5900004BA1 /* Preview Assets.xcassets */; }; 18 | A98D7E7D27219FCD00004BA1 /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98D7E7C27219FCD00004BA1 /* Video.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 422A0DC9273D7205008D83B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 23 | 422A0DCA273D7240008D83B3 /* UIKitVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitVideoPlayerView.swift; sourceTree = ""; }; 24 | A909DA8D273ABF6A009530BF /* SimpleVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleVideoPlayerView.swift; sourceTree = ""; }; 25 | A909DA90273AC08C009530BF /* ManifestVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManifestVideoPlayerView.swift; sourceTree = ""; }; 26 | A98B930E2727FDDA005672FA /* VideoPlayerVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerVM.swift; sourceTree = ""; }; 27 | A98D7E6B27219C5700004BA1 /* StreamingApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StreamingApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | A98D7E6E27219C5700004BA1 /* StreamingApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingApp.swift; sourceTree = ""; }; 29 | A98D7E7027219C5700004BA1 /* BufferVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BufferVideoPlayerView.swift; sourceTree = ""; }; 30 | A98D7E7227219C5900004BA1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | A98D7E7527219C5900004BA1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 32 | A98D7E7C27219FCD00004BA1 /* Video.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Video.swift; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | A98D7E6827219C5700004BA1 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | A909DA8F273ABFC6009530BF /* Views */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | A98D7E7027219C5700004BA1 /* BufferVideoPlayerView.swift */, 50 | A909DA8D273ABF6A009530BF /* SimpleVideoPlayerView.swift */, 51 | A909DA90273AC08C009530BF /* ManifestVideoPlayerView.swift */, 52 | 422A0DCA273D7240008D83B3 /* UIKitVideoPlayerView.swift */, 53 | ); 54 | path = Views; 55 | sourceTree = ""; 56 | }; 57 | A98D7E6227219C5700004BA1 = { 58 | isa = PBXGroup; 59 | children = ( 60 | A98D7E6D27219C5700004BA1 /* StreamingApp */, 61 | A98D7E6C27219C5700004BA1 /* Products */, 62 | ); 63 | sourceTree = ""; 64 | }; 65 | A98D7E6C27219C5700004BA1 /* Products */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | A98D7E6B27219C5700004BA1 /* StreamingApp.app */, 69 | ); 70 | name = Products; 71 | sourceTree = ""; 72 | }; 73 | A98D7E6D27219C5700004BA1 /* StreamingApp */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 422A0DC9273D7205008D83B3 /* Info.plist */, 77 | A909DA8F273ABFC6009530BF /* Views */, 78 | A98D7E6E27219C5700004BA1 /* StreamingApp.swift */, 79 | A98D7E7227219C5900004BA1 /* Assets.xcassets */, 80 | A98D7E7427219C5900004BA1 /* Preview Content */, 81 | A98D7E7C27219FCD00004BA1 /* Video.swift */, 82 | A98B930E2727FDDA005672FA /* VideoPlayerVM.swift */, 83 | ); 84 | path = StreamingApp; 85 | sourceTree = ""; 86 | }; 87 | A98D7E7427219C5900004BA1 /* Preview Content */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | A98D7E7527219C5900004BA1 /* Preview Assets.xcassets */, 91 | ); 92 | path = "Preview Content"; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXNativeTarget section */ 98 | A98D7E6A27219C5700004BA1 /* StreamingApp */ = { 99 | isa = PBXNativeTarget; 100 | buildConfigurationList = A98D7E7927219C5900004BA1 /* Build configuration list for PBXNativeTarget "StreamingApp" */; 101 | buildPhases = ( 102 | A98D7E6727219C5700004BA1 /* Sources */, 103 | A98D7E6827219C5700004BA1 /* Frameworks */, 104 | A98D7E6927219C5700004BA1 /* Resources */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = StreamingApp; 111 | productName = StreamingApp; 112 | productReference = A98D7E6B27219C5700004BA1 /* StreamingApp.app */; 113 | productType = "com.apple.product-type.application"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | A98D7E6327219C5700004BA1 /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | BuildIndependentTargetsInParallel = 1; 122 | LastSwiftUpdateCheck = 1300; 123 | LastUpgradeCheck = 1300; 124 | TargetAttributes = { 125 | A98D7E6A27219C5700004BA1 = { 126 | CreatedOnToolsVersion = 13.0; 127 | }; 128 | }; 129 | }; 130 | buildConfigurationList = A98D7E6627219C5700004BA1 /* Build configuration list for PBXProject "StreamingApp" */; 131 | compatibilityVersion = "Xcode 13.0"; 132 | developmentRegion = en; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | Base, 137 | ); 138 | mainGroup = A98D7E6227219C5700004BA1; 139 | productRefGroup = A98D7E6C27219C5700004BA1 /* Products */; 140 | projectDirPath = ""; 141 | projectRoot = ""; 142 | targets = ( 143 | A98D7E6A27219C5700004BA1 /* StreamingApp */, 144 | ); 145 | }; 146 | /* End PBXProject section */ 147 | 148 | /* Begin PBXResourcesBuildPhase section */ 149 | A98D7E6927219C5700004BA1 /* Resources */ = { 150 | isa = PBXResourcesBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | A98D7E7627219C5900004BA1 /* Preview Assets.xcassets in Resources */, 154 | A98D7E7327219C5900004BA1 /* Assets.xcassets in Resources */, 155 | ); 156 | runOnlyForDeploymentPostprocessing = 0; 157 | }; 158 | /* End PBXResourcesBuildPhase section */ 159 | 160 | /* Begin PBXSourcesBuildPhase section */ 161 | A98D7E6727219C5700004BA1 /* Sources */ = { 162 | isa = PBXSourcesBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | A98D7E7127219C5700004BA1 /* BufferVideoPlayerView.swift in Sources */, 166 | 422A0DCB273D7240008D83B3 /* UIKitVideoPlayerView.swift in Sources */, 167 | A909DA8E273ABF6A009530BF /* SimpleVideoPlayerView.swift in Sources */, 168 | A98B930F2727FDDA005672FA /* VideoPlayerVM.swift in Sources */, 169 | A98D7E7D27219FCD00004BA1 /* Video.swift in Sources */, 170 | A909DA91273AC08C009530BF /* ManifestVideoPlayerView.swift in Sources */, 171 | A98D7E6F27219C5700004BA1 /* StreamingApp.swift in Sources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXSourcesBuildPhase section */ 176 | 177 | /* Begin XCBuildConfiguration section */ 178 | A98D7E7727219C5900004BA1 /* Debug */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ALWAYS_SEARCH_USER_PATHS = NO; 182 | CLANG_ANALYZER_NONNULL = YES; 183 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_ENABLE_OBJC_WEAK = YES; 189 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_COMMA = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 196 | CLANG_WARN_EMPTY_BODY = YES; 197 | CLANG_WARN_ENUM_CONVERSION = YES; 198 | CLANG_WARN_INFINITE_RECURSION = YES; 199 | CLANG_WARN_INT_CONVERSION = YES; 200 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 201 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 202 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 204 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 205 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 206 | CLANG_WARN_STRICT_PROTOTYPES = YES; 207 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 208 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 209 | CLANG_WARN_UNREACHABLE_CODE = YES; 210 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 211 | COPY_PHASE_STRIP = NO; 212 | DEBUG_INFORMATION_FORMAT = dwarf; 213 | ENABLE_STRICT_OBJC_MSGSEND = YES; 214 | ENABLE_TESTABILITY = YES; 215 | GCC_C_LANGUAGE_STANDARD = gnu11; 216 | GCC_DYNAMIC_NO_PIC = NO; 217 | GCC_NO_COMMON_BLOCKS = YES; 218 | GCC_OPTIMIZATION_LEVEL = 0; 219 | GCC_PREPROCESSOR_DEFINITIONS = ( 220 | "DEBUG=1", 221 | "$(inherited)", 222 | ); 223 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 224 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 225 | GCC_WARN_UNDECLARED_SELECTOR = YES; 226 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 227 | GCC_WARN_UNUSED_FUNCTION = YES; 228 | GCC_WARN_UNUSED_VARIABLE = YES; 229 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 230 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 231 | MTL_FAST_MATH = YES; 232 | ONLY_ACTIVE_ARCH = YES; 233 | SDKROOT = iphoneos; 234 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 235 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 236 | }; 237 | name = Debug; 238 | }; 239 | A98D7E7827219C5900004BA1 /* Release */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | ALWAYS_SEARCH_USER_PATHS = NO; 243 | CLANG_ANALYZER_NONNULL = YES; 244 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 246 | CLANG_CXX_LIBRARY = "libc++"; 247 | CLANG_ENABLE_MODULES = YES; 248 | CLANG_ENABLE_OBJC_ARC = YES; 249 | CLANG_ENABLE_OBJC_WEAK = YES; 250 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 251 | CLANG_WARN_BOOL_CONVERSION = YES; 252 | CLANG_WARN_COMMA = YES; 253 | CLANG_WARN_CONSTANT_CONVERSION = YES; 254 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 257 | CLANG_WARN_EMPTY_BODY = YES; 258 | CLANG_WARN_ENUM_CONVERSION = YES; 259 | CLANG_WARN_INFINITE_RECURSION = YES; 260 | CLANG_WARN_INT_CONVERSION = YES; 261 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 263 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 265 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | COPY_PHASE_STRIP = NO; 273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 274 | ENABLE_NS_ASSERTIONS = NO; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu11; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 285 | MTL_ENABLE_DEBUG_INFO = NO; 286 | MTL_FAST_MATH = YES; 287 | SDKROOT = iphoneos; 288 | SWIFT_COMPILATION_MODE = wholemodule; 289 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 290 | VALIDATE_PRODUCT = YES; 291 | }; 292 | name = Release; 293 | }; 294 | A98D7E7A27219C5900004BA1 /* Debug */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 298 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 299 | CODE_SIGN_ENTITLEMENTS = ""; 300 | CODE_SIGN_STYLE = Automatic; 301 | CURRENT_PROJECT_VERSION = 1; 302 | DEVELOPMENT_ASSET_PATHS = "\"StreamingApp/Preview Content\""; 303 | DEVELOPMENT_TEAM = 5CH8N57WM4; 304 | ENABLE_PREVIEWS = YES; 305 | GENERATE_INFOPLIST_FILE = YES; 306 | INFOPLIST_FILE = StreamingApp/Info.plist; 307 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 308 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 309 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 310 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 311 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 312 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 313 | LD_RUNPATH_SEARCH_PATHS = ( 314 | "$(inherited)", 315 | "@executable_path/Frameworks", 316 | ); 317 | MARKETING_VERSION = 1.0; 318 | PRODUCT_BUNDLE_IDENTIFIER = academy.developer..StreamingApp; 319 | PRODUCT_NAME = "$(TARGET_NAME)"; 320 | SWIFT_EMIT_LOC_STRINGS = YES; 321 | SWIFT_VERSION = 5.0; 322 | TARGETED_DEVICE_FAMILY = "1,2"; 323 | }; 324 | name = Debug; 325 | }; 326 | A98D7E7B27219C5900004BA1 /* Release */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 330 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 331 | CODE_SIGN_ENTITLEMENTS = ""; 332 | CODE_SIGN_STYLE = Automatic; 333 | CURRENT_PROJECT_VERSION = 1; 334 | DEVELOPMENT_ASSET_PATHS = "\"StreamingApp/Preview Content\""; 335 | DEVELOPMENT_TEAM = 5CH8N57WM4; 336 | ENABLE_PREVIEWS = YES; 337 | GENERATE_INFOPLIST_FILE = YES; 338 | INFOPLIST_FILE = StreamingApp/Info.plist; 339 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 340 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 341 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 342 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 343 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 344 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 345 | LD_RUNPATH_SEARCH_PATHS = ( 346 | "$(inherited)", 347 | "@executable_path/Frameworks", 348 | ); 349 | MARKETING_VERSION = 1.0; 350 | PRODUCT_BUNDLE_IDENTIFIER = academy.developer..StreamingApp; 351 | PRODUCT_NAME = "$(TARGET_NAME)"; 352 | SWIFT_EMIT_LOC_STRINGS = YES; 353 | SWIFT_VERSION = 5.0; 354 | TARGETED_DEVICE_FAMILY = "1,2"; 355 | }; 356 | name = Release; 357 | }; 358 | /* End XCBuildConfiguration section */ 359 | 360 | /* Begin XCConfigurationList section */ 361 | A98D7E6627219C5700004BA1 /* Build configuration list for PBXProject "StreamingApp" */ = { 362 | isa = XCConfigurationList; 363 | buildConfigurations = ( 364 | A98D7E7727219C5900004BA1 /* Debug */, 365 | A98D7E7827219C5900004BA1 /* Release */, 366 | ); 367 | defaultConfigurationIsVisible = 0; 368 | defaultConfigurationName = Release; 369 | }; 370 | A98D7E7927219C5900004BA1 /* Build configuration list for PBXNativeTarget "StreamingApp" */ = { 371 | isa = XCConfigurationList; 372 | buildConfigurations = ( 373 | A98D7E7A27219C5900004BA1 /* Debug */, 374 | A98D7E7B27219C5900004BA1 /* Release */, 375 | ); 376 | defaultConfigurationIsVisible = 0; 377 | defaultConfigurationName = Release; 378 | }; 379 | /* End XCConfigurationList section */ 380 | }; 381 | rootObject = A98D7E6327219C5700004BA1 /* Project object */; 382 | } 383 | --------------------------------------------------------------------------------