├── 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 |
--------------------------------------------------------------------------------