From 57a5482045b9a2cde86c717f9fffe7d64c726b70 Mon Sep 17 00:00:00 2001 From: "Mr.zhou" <1422157428@qq.com> Date: Wed, 29 May 2024 13:20:07 +0800 Subject: [PATCH] =?UTF-8?q?b=E9=9D=A2=E5=88=9D=E6=AC=A1=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MusicPlayer.xcodeproj/project.pbxproj | 70 +- MusicPlayer/AppDelegate.swift | 19 +- .../Center_Add_'logo.imageset/Contents.json | 22 + .../Group_1597880530@2x.png | Bin 0 -> 1426 bytes .../Group_1597880530@3x.png | Bin 0 -> 2795 bytes .../Center_Top_bg.imageset/Contents.json | 22 + .../Center_Top_bg.imageset/Gradient-09@2x.png | Bin 0 -> 88886 bytes .../Center_Top_bg.imageset/Gradient-09@3x.png | Bin 0 -> 199974 bytes .../Positive/Center/Contents.json | 6 + .../Love_Artists_logo.imageset/Contents.json | 22 + .../Group_1597880746@2x.png | Bin 0 -> 1245 bytes .../Group_1597880746@3x.png | Bin 0 -> 2213 bytes .../Love_Song_logo.imageset/Contents.json | 22 + .../Group_1597880536@2x.png | Bin 0 -> 1421 bytes .../Group_1597880536@3x.png | Bin 0 -> 2542 bytes .../Offline_Songs_logo.imageset/Contents.json | 22 + .../Group_1597880534@2x.png | Bin 0 -> 1678 bytes .../Group_1597880534@3x.png | Bin 0 -> 3063 bytes .../Tag_Delete'logo.imageset/Contents.json | 22 + .../Tag_Delete'logo.imageset/Frame@2x.png | Bin 0 -> 1191 bytes .../Tag_Delete'logo.imageset/Frame@3x.png | Bin 0 -> 2093 bytes MusicPlayer/Info.plist | 16 + .../MusicPlayer.xcdatamodel/contents | 15 +- .../Common/Extension(扩展)/AVPlayerItem.swift | 22 + .../Common/Extension(扩展)/Notification.swift | 8 + .../MP/Common/Extension(扩展)/String.swift | 18 + .../Common/Macro(宏定义与全局量)/Macro.swift | 16 + .../Tool(工具封装)/MPTableManager.swift | 100 - .../Common/Tool(工具封装)/MP_AVURLAsset.swift | 501 ++ .../Tool(工具封装)/MP_CacheManager.swift | 143 + .../Tool(工具封装)/MP_DownloadManager.swift | 121 +- .../Tool(工具封装)/MP_NetWorkManager.swift | 115 +- .../Tool(工具封装)/MP_PlayerManager.swift | 374 +- .../MPPositive_JsonPlayer.swift | 8 + .../MPPositive_CollectionListModel.swift | 4 +- .../MPPositive_CollectionSongModel.swift | 6 +- .../Models/MPPositive_DownloadItemModel.swift | 12 +- .../Models/MPPositive_SearchTagModel.swift | 18 + .../Models/MPPositive_SongItemModel.swift | 17 +- ...MPPositive_CollectionArtistViewModel.swift | 40 + .../MPPositive_CollectionListViewModel.swift | 40 + .../MPPositive_CollectionSongViewModel.swift | 40 + .../MPPositive_DownloadViewModel.swift | 39 + .../MPPositive_SongViewModel.swift | 120 +- .../MPPositive_LoadCoreModel.swift | 91 + .../MPPositive_PlayerLoadViewModel.swift | 63 +- ...PPositive_SearchResultsLoadViewModel.swift | 6 + .../MPPositive_NavigationController.swift | 4 +- .../MPPositive_TabBarController.swift | 37 +- .../MPPositive_LibraryViewController.swift | 224 +- .../MPPositive_LoveArtistsViewController.swift | 78 + .../MPPositive_LoveSongsViewController.swift | 92 + .../MPPositive_OfflineSongsViewController.swift | 95 + .../MPPositive_ArtistShowViewController.swift | 2 +- .../MPPositive_HomeViewController.swift | 15 +- .../MPPositive_ListShowViewController.swift | 30 +- .../MPPositive_MoreContentViewController.swift | 1 + .../MPPositive_PlayerListShowViewController.swift | 1 + .../MPPositive_PlayerViewController.swift | 11 +- .../MPPositive_RecommendViewController.swift | 3 +- ...MPPositive_SearchResultShowViewController.swift | 17 +- .../MPPositive_SearchViewController.swift | 161 +- .../MPPositive_LibraryTableViewCell.swift | 84 + .../MPPositive_LoveArtistTableViewCell.swift | 60 + .../MPPositive_ArtistShowHeaderView.swift | 6 +- .../Home/MPPositive_ArtistShowTypeView.swift | 1 + ...PPositive_MusicItemShowTableViewCell.swift | 28 +- .../Player/MPPositive_PlayerCoverView.swift | 62 +- .../MPPositive_RecommendShowTypeView.swift | 1 + ...Positive_SearchResultPreviewShowView.swift | 1 + ...sitive_SearchResultShowTableViewCell.swift | 49 +- .../MPPositive_SearchResultTypeShowView.swift | 1 + ...Positive_SearchTagCollectionViewCell.swift | 35 + Podfile | 5 +- Podfile.lock | 14 +- .../FreeStreamer/FSAudioController.h | 274 + .../FreeStreamer/FSAudioController.m | 875 ++++ .../FreeStreamer/FreeStreamer/FSAudioStream.h | 600 +++ .../FreeStreamer/FSAudioStream.mm | 1905 +++++++ .../FreeStreamer/FSCheckContentTypeRequest.h | 128 + .../FreeStreamer/FSCheckContentTypeRequest.m | 236 + .../FreeStreamer/FSParsePlaylistRequest.h | 71 + .../FreeStreamer/FSParsePlaylistRequest.m | 325 ++ .../FSParseRssPodcastFeedRequest.h | 28 + .../FSParseRssPodcastFeedRequest.m | 100 + .../FreeStreamer/FSPlaylistItem.h | 41 + .../FreeStreamer/FSPlaylistItem.m | 25 + .../FreeStreamer/FSXMLHttpRequest.h | 112 + .../FreeStreamer/FSXMLHttpRequest.m | 255 + .../FreeStreamer/FreeStreamer/audio_queue.cpp | 566 ++ .../FreeStreamer/FreeStreamer/audio_queue.h | 102 + .../FreeStreamer/audio_stream.cpp | 2229 ++++++++ .../FreeStreamer/FreeStreamer/audio_stream.h | 255 + .../FreeStreamer/caching_stream.cpp | 440 ++ .../FreeStreamer/caching_stream.h | 73 + .../FreeStreamer/FreeStreamer/file_output.cpp | 30 + .../FreeStreamer/FreeStreamer/file_output.h | 32 + .../FreeStreamer/FreeStreamer/file_stream.cpp | 405 ++ .../FreeStreamer/FreeStreamer/file_stream.h | 64 + .../FreeStreamer/FreeStreamer/http_stream.cpp | 908 ++++ .../FreeStreamer/FreeStreamer/http_stream.h | 98 + .../FreeStreamer/FreeStreamer/id3_parser.cpp | 536 ++ .../FreeStreamer/FreeStreamer/id3_parser.h | 44 + .../FreeStreamer/input_stream.cpp | 21 + .../FreeStreamer/FreeStreamer/input_stream.h | 56 + .../FreeStreamer/stream_configuration.cpp | 34 + .../FreeStreamer/stream_configuration.h | 55 + Pods/FreeStreamer/LICENSE.txt | 53 + Pods/FreeStreamer/README.markdown | 41 + Pods/Manifest.lock | 14 +- Pods/Pods.xcodeproj/project.pbxproj | 4617 ++++++++++------- .../Framework/PrivacyInfo.xcprivacy | 14 + Pods/Reachability/LICENCE.txt | 24 + Pods/Reachability/README.md | 159 + Pods/Reachability/Reachability.h | 103 + Pods/Reachability/Reachability.m | 508 ++ .../FreeStreamer/FreeStreamer-Info.plist | 26 + .../FreeStreamer/FreeStreamer-dummy.m | 5 + .../FreeStreamer/FreeStreamer-prefix.pch | 12 + .../FreeStreamer/FreeStreamer-umbrella.h | 23 + .../FreeStreamer/FreeStreamer.debug.xcconfig | 15 + .../FreeStreamer/FreeStreamer.modulemap | 6 + .../FreeStreamer.release.xcconfig | 15 + ...Pods-MusicPlayer-acknowledgements.markdown | 94 + .../Pods-MusicPlayer-acknowledgements.plist | 112 + ...er-frameworks-Debug-input-files.xcfilelist | 5 +- ...r-frameworks-Debug-output-files.xcfilelist | 5 +- ...-frameworks-Release-input-files.xcfilelist | 5 +- ...frameworks-Release-output-files.xcfilelist | 5 +- .../Pods-MusicPlayer-frameworks.sh | 6 + .../Pods-MusicPlayer.debug.xcconfig | 6 +- .../Pods-MusicPlayer.release.xcconfig | 6 +- .../Reachability/Reachability-Info.plist | 26 + .../Reachability/Reachability-dummy.m | 5 + .../Reachability/Reachability-prefix.pch | 12 + .../Reachability/Reachability-umbrella.h | 17 + .../Reachability/Reachability.debug.xcconfig | 13 + .../Reachability/Reachability.modulemap | 6 + .../Reachability.release.xcconfig | 13 + ...achability_Privacy-Reachability-Info.plist | 24 + .../Tiercel/Tiercel-Info.plist | 26 + .../Tiercel/Tiercel-dummy.m | 5 + .../Tiercel/Tiercel-prefix.pch | 12 + .../Tiercel/Tiercel-umbrella.h | 16 + .../Tiercel/Tiercel.debug.xcconfig | 14 + .../Tiercel/Tiercel.modulemap | 6 + .../Tiercel/Tiercel.release.xcconfig | 14 + Pods/Tiercel/LICENSE | 19 + Pods/Tiercel/README.md | 206 + .../Sources/Extensions/Array+Safe.swift | 38 + .../Extensions/CodingUserInfoKey+Cache.swift | 16 + .../Sources/Extensions/Data+Hash.swift | 64 + .../Extensions/DispatchQueue+Safe.swift | 42 + .../Sources/Extensions/Double+TaskInfo.swift | 42 + .../FileManager+AvailableCapacity.swift | 48 + .../Sources/Extensions/Int64+TaskInfo.swift | 59 + .../OperationQueue+DispatchQueue.swift | 40 + .../Sources/Extensions/String+Hash.swift | 59 + .../Extensions/URLSession+ResumeData.swift | 50 + Pods/Tiercel/Sources/General/Cache.swift | 389 ++ Pods/Tiercel/Sources/General/Common.swift | 111 + .../Sources/General/DownloadTask.swift | 606 +++ Pods/Tiercel/Sources/General/Executer.swift | 52 + .../Sources/General/Notifications.swift | 82 + Pods/Tiercel/Sources/General/Protected.swift | 190 + .../General/SessionConfiguration.swift | 64 + .../Sources/General/SessionDelegate.swift | 96 + .../Sources/General/SessionManager.swift | 1009 ++++ Pods/Tiercel/Sources/General/Task.swift | 363 ++ .../Sources/General/TiercelError.swift | 127 + .../Sources/General/URLConvertible.swift | 55 + .../Sources/Utility/FileChecksumHelper.swift | 109 + .../Sources/Utility/ResumeDataHelper.swift | 198 + 173 files changed, 21979 insertions(+), 2464 deletions(-) create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Contents.json create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Group_1597880530@2x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Group_1597880530@3x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Contents.json create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Gradient-09@2x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Gradient-09@3x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Contents.json create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Contents.json create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Group_1597880746@2x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Group_1597880746@3x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Contents.json create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Group_1597880536@2x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Group_1597880536@3x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Contents.json create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Group_1597880534@2x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Group_1597880534@3x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Search/Tag_Delete'logo.imageset/Contents.json create mode 100644 MusicPlayer/Assets.xcassets/Positive/Search/Tag_Delete'logo.imageset/Frame@2x.png create mode 100644 MusicPlayer/Assets.xcassets/Positive/Search/Tag_Delete'logo.imageset/Frame@3x.png create mode 100644 MusicPlayer/MP/Common/Extension(扩展)/AVPlayerItem.swift delete mode 100644 MusicPlayer/MP/Common/Tool(工具封装)/MPTableManager.swift create mode 100644 MusicPlayer/MP/Common/Tool(工具封装)/MP_AVURLAsset.swift create mode 100644 MusicPlayer/MP/Common/Tool(工具封装)/MP_CacheManager.swift create mode 100644 MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SearchTagModel.swift create mode 100644 MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionArtistViewModel.swift create mode 100644 MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionListViewModel.swift create mode 100644 MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionSongViewModel.swift create mode 100644 MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_DownloadViewModel.swift create mode 100644 MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_LoadCoreModel.swift create mode 100644 MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveArtistsViewController.swift create mode 100644 MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveSongsViewController.swift create mode 100644 MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift create mode 100644 MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LibraryTableViewCell.swift create mode 100644 MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LoveArtistTableViewCell.swift create mode 100644 MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchTagCollectionViewCell.swift create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.m create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.mm create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.m create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.m create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.m create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.m create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.m create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.h create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.cpp create mode 100644 Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.h create mode 100644 Pods/FreeStreamer/LICENSE.txt create mode 100644 Pods/FreeStreamer/README.markdown create mode 100644 Pods/Reachability/Framework/PrivacyInfo.xcprivacy create mode 100644 Pods/Reachability/LICENCE.txt create mode 100644 Pods/Reachability/README.md create mode 100644 Pods/Reachability/Reachability.h create mode 100644 Pods/Reachability/Reachability.m create mode 100644 Pods/Target Support Files/FreeStreamer/FreeStreamer-Info.plist create mode 100644 Pods/Target Support Files/FreeStreamer/FreeStreamer-dummy.m create mode 100644 Pods/Target Support Files/FreeStreamer/FreeStreamer-prefix.pch create mode 100644 Pods/Target Support Files/FreeStreamer/FreeStreamer-umbrella.h create mode 100644 Pods/Target Support Files/FreeStreamer/FreeStreamer.debug.xcconfig create mode 100644 Pods/Target Support Files/FreeStreamer/FreeStreamer.modulemap create mode 100644 Pods/Target Support Files/FreeStreamer/FreeStreamer.release.xcconfig create mode 100644 Pods/Target Support Files/Reachability/Reachability-Info.plist create mode 100644 Pods/Target Support Files/Reachability/Reachability-dummy.m create mode 100644 Pods/Target Support Files/Reachability/Reachability-prefix.pch create mode 100644 Pods/Target Support Files/Reachability/Reachability-umbrella.h create mode 100644 Pods/Target Support Files/Reachability/Reachability.debug.xcconfig create mode 100644 Pods/Target Support Files/Reachability/Reachability.modulemap create mode 100644 Pods/Target Support Files/Reachability/Reachability.release.xcconfig create mode 100644 Pods/Target Support Files/Reachability/ResourceBundle-Reachability_Privacy-Reachability-Info.plist create mode 100644 Pods/Target Support Files/Tiercel/Tiercel-Info.plist create mode 100644 Pods/Target Support Files/Tiercel/Tiercel-dummy.m create mode 100644 Pods/Target Support Files/Tiercel/Tiercel-prefix.pch create mode 100644 Pods/Target Support Files/Tiercel/Tiercel-umbrella.h create mode 100644 Pods/Target Support Files/Tiercel/Tiercel.debug.xcconfig create mode 100644 Pods/Target Support Files/Tiercel/Tiercel.modulemap create mode 100644 Pods/Target Support Files/Tiercel/Tiercel.release.xcconfig create mode 100644 Pods/Tiercel/LICENSE create mode 100644 Pods/Tiercel/README.md create mode 100644 Pods/Tiercel/Sources/Extensions/Array+Safe.swift create mode 100644 Pods/Tiercel/Sources/Extensions/CodingUserInfoKey+Cache.swift create mode 100644 Pods/Tiercel/Sources/Extensions/Data+Hash.swift create mode 100644 Pods/Tiercel/Sources/Extensions/DispatchQueue+Safe.swift create mode 100644 Pods/Tiercel/Sources/Extensions/Double+TaskInfo.swift create mode 100644 Pods/Tiercel/Sources/Extensions/FileManager+AvailableCapacity.swift create mode 100644 Pods/Tiercel/Sources/Extensions/Int64+TaskInfo.swift create mode 100644 Pods/Tiercel/Sources/Extensions/OperationQueue+DispatchQueue.swift create mode 100644 Pods/Tiercel/Sources/Extensions/String+Hash.swift create mode 100644 Pods/Tiercel/Sources/Extensions/URLSession+ResumeData.swift create mode 100644 Pods/Tiercel/Sources/General/Cache.swift create mode 100644 Pods/Tiercel/Sources/General/Common.swift create mode 100644 Pods/Tiercel/Sources/General/DownloadTask.swift create mode 100644 Pods/Tiercel/Sources/General/Executer.swift create mode 100644 Pods/Tiercel/Sources/General/Notifications.swift create mode 100644 Pods/Tiercel/Sources/General/Protected.swift create mode 100644 Pods/Tiercel/Sources/General/SessionConfiguration.swift create mode 100644 Pods/Tiercel/Sources/General/SessionDelegate.swift create mode 100644 Pods/Tiercel/Sources/General/SessionManager.swift create mode 100644 Pods/Tiercel/Sources/General/Task.swift create mode 100644 Pods/Tiercel/Sources/General/TiercelError.swift create mode 100644 Pods/Tiercel/Sources/General/URLConvertible.swift create mode 100644 Pods/Tiercel/Sources/Utility/FileChecksumHelper.swift create mode 100644 Pods/Tiercel/Sources/Utility/ResumeDataHelper.swift diff --git a/MusicPlayer.xcodeproj/project.pbxproj b/MusicPlayer.xcodeproj/project.pbxproj index a1c040d..ac9325b 100644 --- a/MusicPlayer.xcodeproj/project.pbxproj +++ b/MusicPlayer.xcodeproj/project.pbxproj @@ -32,6 +32,16 @@ CB102F5C2BFB244500E967D8 /* MPPositive_RecommendMemberCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F5B2BFB244500E967D8 /* MPPositive_RecommendMemberCollectionViewCell.swift */; }; CB102F5E2BFB2F7C00E967D8 /* MPPositive_RecommendShowTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB102F5D2BFB2F7C00E967D8 /* MPPositive_RecommendShowTypeView.swift */; }; CB1C16522BC80BF100B96AB3 /* MPSideA_MediaCenterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1C16512BC80BF100B96AB3 /* MPSideA_MediaCenterManager.swift */; }; + CB24168D2C05D09C007877F7 /* MPPositive_LibraryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24168C2C05D09C007877F7 /* MPPositive_LibraryTableViewCell.swift */; }; + CB24168F2C05D2DD007877F7 /* MPPositive_LoadCoreModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24168E2C05D2DD007877F7 /* MPPositive_LoadCoreModel.swift */; }; + CB2416912C05D36F007877F7 /* MPPositive_DownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2416902C05D36F007877F7 /* MPPositive_DownloadViewModel.swift */; }; + CB2416932C05D388007877F7 /* MPPositive_CollectionSongViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2416922C05D388007877F7 /* MPPositive_CollectionSongViewModel.swift */; }; + CB2416952C05D3A6007877F7 /* MPPositive_CollectionListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2416942C05D3A6007877F7 /* MPPositive_CollectionListViewModel.swift */; }; + CB2416972C05D3C3007877F7 /* MPPositive_CollectionArtistViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2416962C05D3C3007877F7 /* MPPositive_CollectionArtistViewModel.swift */; }; + CB2416992C05DFC1007877F7 /* MPPositive_LoveArtistsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2416982C05DFC1007877F7 /* MPPositive_LoveArtistsViewController.swift */; }; + CB24169B2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24169A2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift */; }; + CB24169D2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24169C2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift */; }; + CB24169F2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24169E2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift */; }; CB5661292BE09D0500CFD014 /* MPPositive_JsonPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */; }; CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB56612C2BE0DF8C00CFD014 /* MP_WebWork.swift */; }; CBB5D31D2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5D31C2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift */; }; @@ -44,6 +54,8 @@ CBB75B0B2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */; }; CBB9F9DD2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB9F9DC2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift */; }; CBB9F9DF2BEDDCC5008338DE /* MP_PlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB9F9DE2BEDDCC5008338DE /* MP_PlayerManager.swift */; }; + CBBA6A222BFF12030047ADF8 /* MP_AVURLAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA6A212BFF12030047ADF8 /* MP_AVURLAsset.swift */; }; + CBBA6A242BFF160C0047ADF8 /* MP_CacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBA6A232BFF160C0047ADF8 /* MP_CacheManager.swift */; }; CBBFA9182BBA83BA00057FD5 /* MP_CoreDataHandlerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBFA9172BBA83BA00057FD5 /* MP_CoreDataHandlerManager.swift */; }; CBBFA91A2BBA846600057FD5 /* CoreDataDelegete.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBFA9192BBA846600057FD5 /* CoreDataDelegete.swift */; }; CBBFA91E2BBA9B5C00057FD5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBFA91D2BBA9B5C00057FD5 /* Notification.swift */; }; @@ -66,7 +78,6 @@ CBC54E642BC4D5D3003B1901 /* Seawater Surging.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CBC54E542BC4D5D3003B1901 /* Seawater Surging.mp3 */; }; CBC54E652BC4D5D3003B1901 /* Summer Insects.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CBC54E552BC4D5D3003B1901 /* Summer Insects.mp3 */; }; CBC54E672BC4D90F003B1901 /* Resource.plist in Resources */ = {isa = PBXBuildFile; fileRef = CBC54E662BC4D90F003B1901 /* Resource.plist */; }; - CBC687492BC2882B0023ECA6 /* MPTableManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC687482BC2882B0023ECA6 /* MPTableManager.swift */; }; CBC6874B2BC2B0710023ECA6 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC6874A2BC2B0710023ECA6 /* String.swift */; }; CBCAFB5A2BB3C2A000BC6520 /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCAFB592BB3C2A000BC6520 /* LayoutConstraint.swift */; }; CBCAFB5D2BB3C52100BC6520 /* HexColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCAFB5C2BB3C52100BC6520 /* HexColor.swift */; }; @@ -134,6 +145,7 @@ CBCC234F2BEE57AC004D7A57 /* MPPositive_PresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCC234E2BEE57AC004D7A57 /* MPPositive_PresentationController.swift */; }; CBCC23512BEE58C1004D7A57 /* MPPositive_PlayerListShowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCC23502BEE58C1004D7A57 /* MPPositive_PlayerListShowViewController.swift */; }; CBCC23532BEE596E004D7A57 /* MPPositive_PlayerListShowTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCC23522BEE596E004D7A57 /* MPPositive_PlayerListShowTableViewCell.swift */; }; + CBCF94D42BFED7AD0069EE0B /* AVPlayerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCF94D32BFED7AD0069EE0B /* AVPlayerItem.swift */; }; CBD0CC592BDA238100C4B64D /* MPPositive_BrowseLoadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD0CC582BDA238100C4B64D /* MPPositive_BrowseLoadViewModel.swift */; }; CBD0CC5E2BDA260500C4B64D /* MPPositive_BrowseItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD0CC5D2BDA260500C4B64D /* MPPositive_BrowseItemViewModel.swift */; }; CBD313532BD60CD80015D227 /* MPPositive_HomeShowTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD313522BD60CD80015D227 /* MPPositive_HomeShowTableViewCell.swift */; }; @@ -158,6 +170,8 @@ CBD958D42BB6942F00666B0D /* MPSideA_VolumeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD958D32BB6942F00666B0D /* MPSideA_VolumeManager.swift */; }; CBDD516D2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD516C2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift */; }; CBDD516F2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD516E2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift */; }; + CBE10CB52C0629B50068A396 /* MPPositive_SearchTagModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE10CB42C0629B50068A396 /* MPPositive_SearchTagModel.swift */; }; + CBE10CB72C06373A0068A396 /* MPPositive_SearchTagCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE10CB62C06373A0068A396 /* MPPositive_SearchTagCollectionViewCell.swift */; }; CBE16B952BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE16B942BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift */; }; CBE1CB442BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE1CB432BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift */; }; CBE1CB4A2BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBE1CB492BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift */; }; @@ -222,6 +236,16 @@ CB102F5B2BFB244500E967D8 /* MPPositive_RecommendMemberCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_RecommendMemberCollectionViewCell.swift; sourceTree = ""; }; CB102F5D2BFB2F7C00E967D8 /* MPPositive_RecommendShowTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_RecommendShowTypeView.swift; sourceTree = ""; }; CB1C16512BC80BF100B96AB3 /* MPSideA_MediaCenterManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPSideA_MediaCenterManager.swift; sourceTree = ""; }; + CB24168C2C05D09C007877F7 /* MPPositive_LibraryTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LibraryTableViewCell.swift; sourceTree = ""; }; + CB24168E2C05D2DD007877F7 /* MPPositive_LoadCoreModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LoadCoreModel.swift; sourceTree = ""; }; + CB2416902C05D36F007877F7 /* MPPositive_DownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_DownloadViewModel.swift; sourceTree = ""; }; + CB2416922C05D388007877F7 /* MPPositive_CollectionSongViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_CollectionSongViewModel.swift; sourceTree = ""; }; + CB2416942C05D3A6007877F7 /* MPPositive_CollectionListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_CollectionListViewModel.swift; sourceTree = ""; }; + CB2416962C05D3C3007877F7 /* MPPositive_CollectionArtistViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_CollectionArtistViewModel.swift; sourceTree = ""; }; + CB2416982C05DFC1007877F7 /* MPPositive_LoveArtistsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LoveArtistsViewController.swift; sourceTree = ""; }; + CB24169A2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LoveArtistTableViewCell.swift; sourceTree = ""; }; + CB24169C2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LoveSongsViewController.swift; sourceTree = ""; }; + CB24169E2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_OfflineSongsViewController.swift; sourceTree = ""; }; CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonPlayer.swift; sourceTree = ""; }; CB56612C2BE0DF8C00CFD014 /* MP_WebWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_WebWork.swift; sourceTree = ""; }; CBB5D31C2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_MusicItemShowTableViewCell.swift; sourceTree = ""; }; @@ -234,6 +258,8 @@ CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_DownloadItemModel.swift; sourceTree = ""; }; CBB9F9DC2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonLyrics.swift; sourceTree = ""; }; CBB9F9DE2BEDDCC5008338DE /* MP_PlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_PlayerManager.swift; sourceTree = ""; }; + CBBA6A212BFF12030047ADF8 /* MP_AVURLAsset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_AVURLAsset.swift; sourceTree = ""; }; + CBBA6A232BFF160C0047ADF8 /* MP_CacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_CacheManager.swift; sourceTree = ""; }; CBBFA9172BBA83BA00057FD5 /* MP_CoreDataHandlerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_CoreDataHandlerManager.swift; sourceTree = ""; }; CBBFA9192BBA846600057FD5 /* CoreDataDelegete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataDelegete.swift; sourceTree = ""; }; CBBFA91D2BBA9B5C00057FD5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; @@ -256,7 +282,6 @@ CBC54E542BC4D5D3003B1901 /* Seawater Surging.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "Seawater Surging.mp3"; sourceTree = ""; }; CBC54E552BC4D5D3003B1901 /* Summer Insects.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "Summer Insects.mp3"; sourceTree = ""; }; CBC54E662BC4D90F003B1901 /* Resource.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Resource.plist; sourceTree = ""; }; - CBC687482BC2882B0023ECA6 /* MPTableManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPTableManager.swift; sourceTree = ""; }; CBC6874A2BC2B0710023ECA6 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; CBCAFB592BB3C2A000BC6520 /* LayoutConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutConstraint.swift; sourceTree = ""; }; CBCAFB5C2BB3C52100BC6520 /* HexColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexColor.swift; sourceTree = ""; }; @@ -324,6 +349,7 @@ CBCC234E2BEE57AC004D7A57 /* MPPositive_PresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PresentationController.swift; sourceTree = ""; }; CBCC23502BEE58C1004D7A57 /* MPPositive_PlayerListShowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerListShowViewController.swift; sourceTree = ""; }; CBCC23522BEE596E004D7A57 /* MPPositive_PlayerListShowTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerListShowTableViewCell.swift; sourceTree = ""; }; + CBCF94D32BFED7AD0069EE0B /* AVPlayerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItem.swift; sourceTree = ""; }; CBD0CC582BDA238100C4B64D /* MPPositive_BrowseLoadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_BrowseLoadViewModel.swift; sourceTree = ""; }; CBD0CC5D2BDA260500C4B64D /* MPPositive_BrowseItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_BrowseItemViewModel.swift; sourceTree = ""; }; CBD313522BD60CD80015D227 /* MPPositive_HomeShowTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_HomeShowTableViewCell.swift; sourceTree = ""; }; @@ -348,6 +374,8 @@ CBD958D32BB6942F00666B0D /* MPSideA_VolumeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPSideA_VolumeManager.swift; sourceTree = ""; }; CBDD516C2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonNext.swift; sourceTree = ""; }; CBDD516E2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_PlayerLoadViewModel.swift; sourceTree = ""; }; + CBE10CB42C0629B50068A396 /* MPPositive_SearchTagModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchTagModel.swift; sourceTree = ""; }; + CBE10CB62C06373A0068A396 /* MPPositive_SearchTagCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchTagCollectionViewCell.swift; sourceTree = ""; }; CBE16B942BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_SearchSuggestionItemListModel.swift; sourceTree = ""; }; CBE1CB432BDDEAAD00701D57 /* MPPositive_MoreContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_MoreContentViewController.swift; sourceTree = ""; }; CBE1CB492BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_MoreListContentCollectionViewCell.swift; sourceTree = ""; }; @@ -729,11 +757,12 @@ CBE1CB4B2BDE440E00701D57 /* MPPositive_ListHeaderModel.swift */, CBD6F2152BF48DDD00343A4A /* MPPositive_ArtistHeaderModel.swift */, CBB5D31E2BDF711600CC333D /* MPPositive_SongItemModel.swift */, + CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */, CBB75B0A2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift */, CBB5F1F82BFC35D000CBF73A /* MPPositive_CollectionSongModel.swift */, CBB5F1FA2BFC3DB600CBF73A /* MPPositive_CollectionListModel.swift */, CBB5F1FC2BFC40E400CBF73A /* MPPositive_CollectionArtistModel.swift */, - CBFECE382BF0CFF900E07DC4 /* MPPositive_SearchSuggestionItemModel.swift */, + CBE10CB42C0629B50068A396 /* MPPositive_SearchTagModel.swift */, ); path = Models; sourceTree = ""; @@ -752,6 +781,10 @@ CBE16B942BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift */, CBF456DC2BF1E72F00ABF761 /* MPPositive_SearchResultListViewModel.swift */, CBF456E02BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift */, + CB2416902C05D36F007877F7 /* MPPositive_DownloadViewModel.swift */, + CB2416922C05D388007877F7 /* MPPositive_CollectionSongViewModel.swift */, + CB2416942C05D3A6007877F7 /* MPPositive_CollectionListViewModel.swift */, + CB2416962C05D3C3007877F7 /* MPPositive_CollectionArtistViewModel.swift */, ); path = ListViewModels; sourceTree = ""; @@ -763,6 +796,7 @@ CBDD516E2BECBA6E000F12C5 /* MPPositive_PlayerLoadViewModel.swift */, CBF456E22BF2086600ABF761 /* MPPositive_SearchResultsLoadViewModel.swift */, CB102F592BFB002C00E967D8 /* MPPositive_RecommendLoadViewModel.swift */, + CB24168E2C05D2DD007877F7 /* MPPositive_LoadCoreModel.swift */, ); path = LoadViewModels; sourceTree = ""; @@ -846,6 +880,9 @@ isa = PBXGroup; children = ( CB0918A22BD26B2F006D2B39 /* MPPositive_LibraryViewController.swift */, + CB2416982C05DFC1007877F7 /* MPPositive_LoveArtistsViewController.swift */, + CB24169C2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift */, + CB24169E2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift */, ); path = "Center(个人曲库页)"; sourceTree = ""; @@ -862,6 +899,8 @@ CBCB50202BD118BB009760B3 /* Center */ = { isa = PBXGroup; children = ( + CB24168C2C05D09C007877F7 /* MPPositive_LibraryTableViewCell.swift */, + CB24169A2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift */, ); path = Center; sourceTree = ""; @@ -869,6 +908,7 @@ CBCB50212BD118BB009760B3 /* Search */ = { isa = PBXGroup; children = ( + CBE10CB62C06373A0068A396 /* MPPositive_SearchTagCollectionViewCell.swift */, CBF456EA2BF222EC00ABF761 /* MPPositive_SearchSuggestionsView.swift */, CBFECE342BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift */, CBF456EC2BF2253D00ABF761 /* MPPositive_SearchResultsShowView.swift */, @@ -963,11 +1003,12 @@ CBE2C4CA2BC7BE5D00F283A7 /* MP_NetWorkManager.swift */, CB56612C2BE0DF8C00CFD014 /* MP_WebWork.swift */, CBB9F9DE2BEDDCC5008338DE /* MP_PlayerManager.swift */, + CBBA6A212BFF12030047ADF8 /* MP_AVURLAsset.swift */, + CBBA6A232BFF160C0047ADF8 /* MP_CacheManager.swift */, CBCB32192BD7578500802900 /* MP_LocationManager.swift */, CBBFA9172BBA83BA00057FD5 /* MP_CoreDataHandlerManager.swift */, CBE2C4C62BC783F700F283A7 /* MP_HUD.swift */, CBB5F1FE2BFCB40000CBF73A /* MPPositive_Debouncer.swift */, - CBC687482BC2882B0023ECA6 /* MPTableManager.swift */, CBD958D12BB6600500666B0D /* MP_PlayerSlider.swift */, CB102F532BFAFA7200E967D8 /* MP_CircularProgressView.swift */, CB102F542BFAFA7200E967D8 /* MP_DownloadManager.swift */, @@ -985,6 +1026,7 @@ CBBFA91D2BBA9B5C00057FD5 /* Notification.swift */, CBD5AEE02BBBE45300BF5A43 /* ImagePicker.swift */, CBE2C4C82BC7B25800F283A7 /* TableView.swift */, + CBCF94D32BFED7AD0069EE0B /* AVPlayerItem.swift */, ); path = "Extension(扩展)"; sourceTree = ""; @@ -1203,6 +1245,7 @@ CBD6F21E2BF4B61F00343A4A /* MPPositive_ArtistShowTypeView.swift in Sources */, CBCB4FEA2BD11402009760B3 /* MPSideA_MusicModel.swift in Sources */, CBBFA91A2BBA846600057FD5 /* CoreDataDelegete.swift in Sources */, + CBE10CB52C0629B50068A396 /* MPPositive_SearchTagModel.swift in Sources */, CBF456E32BF2086600ABF761 /* MPPositive_SearchResultsLoadViewModel.swift in Sources */, CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */, CBFECE352BF0847F00E07DC4 /* MPPositive_SearchSuggestionItemTableViewCell.swift in Sources */, @@ -1214,7 +1257,9 @@ CBCB50162BD11402009760B3 /* MPSideA_Home_HeadBannerView.swift in Sources */, CBCC234F2BEE57AC004D7A57 /* MPPositive_PresentationController.swift in Sources */, CB102F5A2BFB002C00E967D8 /* MPPositive_RecommendLoadViewModel.swift in Sources */, + CB24169B2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift in Sources */, CBCB500A2BD11402009760B3 /* MPSideA_CustomTabBar.swift in Sources */, + CB2416912C05D36F007877F7 /* MPPositive_DownloadViewModel.swift in Sources */, CBCAFB692BB3CAC400BC6520 /* MP_Lunch_ProgressView.swift in Sources */, CBD6F2182BF4A29B00343A4A /* MPPositive_ArtistViewModel.swift in Sources */, CBCB50102BD11402009760B3 /* MPSideA_SettingTableViewCell.swift in Sources */, @@ -1232,11 +1277,13 @@ CBCB4FFA2BD11402009760B3 /* MPSideA_PrivacyViewController.swift in Sources */, CBD6F21C2BF4AEE600343A4A /* MPPositive_ArtistShowHeaderView.swift in Sources */, CBCB500E2BD11402009760B3 /* MPSideA_CenterTableViewCell.swift in Sources */, + CBBA6A222BFF12030047ADF8 /* MP_AVURLAsset.swift in Sources */, CB102F5E2BFB2F7C00E967D8 /* MPPositive_RecommendShowTypeView.swift in Sources */, 009662312BB14A5A00FCA65F /* ViewController.swift in Sources */, CBEB01852BF5DB3400D45006 /* MPPositive_ArtistDescriptionTableViewCell.swift in Sources */, CBE2C4C72BC783F700F283A7 /* MP_HUD.swift in Sources */, CBE2C4C92BC7B25800F283A7 /* TableView.swift in Sources */, + CB24168F2C05D2DD007877F7 /* MPPositive_LoadCoreModel.swift in Sources */, CBCB4F9A2BD11089009760B3 /* MP_NavigationController.swift in Sources */, CB0918A32BD26B2F006D2B39 /* MPPositive_LibraryViewController.swift in Sources */, CBEE8E322BEB0FC0007DA798 /* MPPositive_PlayerCoverView.swift in Sources */, @@ -1244,15 +1291,19 @@ CBF456E92BF21E0E00ABF761 /* MPPositive_SearchResultPreviewShowView.swift in Sources */, CBF456E12BF1EB4300ABF761 /* MPPositive_SearchResultItemViewModel.swift in Sources */, CBE1CB4E2BDE4BD800701D57 /* MPPositive_ListAlbumListViewModel.swift in Sources */, + CB2416972C05D3C3007877F7 /* MPPositive_CollectionArtistViewModel.swift in Sources */, CBD313572BD63B390015D227 /* MPPositive_HomeListSecondCollectionViewCell.swift in Sources */, 0096622D2BB14A5A00FCA65F /* AppDelegate.swift in Sources */, CBC32A532BD8D9F300687171 /* MPPositive_BrowseItemModel.swift in Sources */, CBE16B952BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift in Sources */, + CB24169F2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift in Sources */, CB102F552BFAFA7200E967D8 /* MP_CircularProgressView.swift in Sources */, CBCB4FEC2BD11402009760B3 /* MPSideA_AddViewController.swift in Sources */, CBB75B0B2BEF0BC400B3FF9A /* MPPositive_DownloadItemModel.swift in Sources */, + CBCF94D42BFED7AD0069EE0B /* AVPlayerItem.swift in Sources */, CBCB50172BD11402009760B3 /* MPSideA_Home_RowListsTableViewCell.swift in Sources */, CBCB4F982BD11054009760B3 /* MP_BaseViewController.swift in Sources */, + CB2416932C05D388007877F7 /* MPPositive_CollectionSongViewModel.swift in Sources */, CBD0CC5E2BDA260500C4B64D /* MPPositive_BrowseItemViewModel.swift in Sources */, CBD313612BD6453A0015D227 /* MPPositive_HomeListFifthCollectionViewCell.swift in Sources */, CBE1CB4A2BDDEBF000701D57 /* MPPositive_MoreListContentCollectionViewCell.swift in Sources */, @@ -1264,10 +1315,12 @@ CBCB50062BD11402009760B3 /* MPSideA_PlayerViewController.swift in Sources */, CBCB4FE92BD11402009760B3 /* MPSideA_LoadDataMusic.swift in Sources */, CBCB4FEE2BD11402009760B3 /* MPSideA_BaseViewController.swift in Sources */, + CB2416992C05DFC1007877F7 /* MPPositive_LoveArtistsViewController.swift in Sources */, CBE1CB582BDE550800701D57 /* MPPositive_ListShowViewController.swift in Sources */, CBCB500B2BD11402009760B3 /* MPSideA_CustomTabBarItem.swift in Sources */, CBB5D31D2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift in Sources */, CBEB017D2BF5D35700D45006 /* MPPositive_ArtistShowSongTableViewCell.swift in Sources */, + CB2416952C05D3A6007877F7 /* MPPositive_CollectionListViewModel.swift in Sources */, CBCC23512BEE58C1004D7A57 /* MPPositive_PlayerListShowViewController.swift in Sources */, CBDD516D2BEC6AFE000F12C5 /* MPPositive_JsonNext.swift in Sources */, CBB5F1F92BFC35D000CBF73A /* MPPositive_CollectionSongModel.swift in Sources */, @@ -1284,10 +1337,11 @@ CBD958D22BB6600500666B0D /* MP_PlayerSlider.swift in Sources */, CBCC23532BEE596E004D7A57 /* MPPositive_PlayerListShowTableViewCell.swift in Sources */, CBB5F1FD2BFC40E400CBF73A /* MPPositive_CollectionArtistModel.swift in Sources */, - CBC687492BC2882B0023ECA6 /* MPTableManager.swift in Sources */, + CB24168D2C05D09C007877F7 /* MPPositive_LibraryTableViewCell.swift in Sources */, CBD6F2142BF44D8A00343A4A /* MPPositive_JsonArtist.swift in Sources */, CBD313532BD60CD80015D227 /* MPPositive_HomeShowTableViewCell.swift in Sources */, CBD6F2162BF48DDD00343A4A /* MPPositive_ArtistHeaderModel.swift in Sources */, + CB24169D2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift in Sources */, CB0918972BD25D8C006D2B39 /* MPPositive_TabBarController.swift in Sources */, CBCB500C2BD11402009760B3 /* MPSideA_CustomTabBarView.swift in Sources */, CBB9F9DD2BEDCFEE008338DE /* MPPositive_JsonLyrics.swift in Sources */, @@ -1303,9 +1357,11 @@ CBCB321A2BD7578500802900 /* MP_LocationManager.swift in Sources */, CBCB4FEB2BD11402009760B3 /* MPSideA_MusicViewModel.swift in Sources */, CBCB4FF02BD11402009760B3 /* MPSideA_PresentationController.swift in Sources */, + CBE10CB72C06373A0068A396 /* MPPositive_SearchTagCollectionViewCell.swift in Sources */, CBFECE3F2BF1176B00E07DC4 /* MPPositive_JsonSearchResults.swift in Sources */, CBC6874B2BC2B0710023ECA6 /* String.swift in Sources */, CBD3135F2BD642D90015D227 /* MPPositive_HomeListFourthCollectionViewCell.swift in Sources */, + CBBA6A242BFF160C0047ADF8 /* MP_CacheManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1459,7 +1515,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1.2; - DEVELOPMENT_TEAM = 6HWQW9JC74; + DEVELOPMENT_TEAM = T93S37G27F; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MusicPlayer/Info.plist; @@ -1497,7 +1553,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1.2; - DEVELOPMENT_TEAM = 6HWQW9JC74; + DEVELOPMENT_TEAM = T93S37G27F; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = MusicPlayer/Info.plist; diff --git a/MusicPlayer/AppDelegate.swift b/MusicPlayer/AppDelegate.swift index d8a2020..fae7bca 100644 --- a/MusicPlayer/AppDelegate.swift +++ b/MusicPlayer/AppDelegate.swift @@ -9,10 +9,13 @@ import UIKit import CoreData import AVFoundation import Alamofire +import Tiercel @_exported import IQKeyboardManagerSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? + // 用来保存由系统提供的完成处理器 + var backgroundSessionCompletionHandler: (() -> Void)? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { //请求通知权限 UNUserNotificationCenter.current() @@ -22,6 +25,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { print("Users are not allowed to be notified of messages.") } } + //启动前销毁所有的下载任务 + DownloadManager.shared.cancelAllTasksIfNeeded() setAudioSupport() MP_NetWorkManager.shared.requestStatusToYouTube() IQKeyboardManager.shared.enable = true @@ -32,15 +37,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { - DownloadManager.shared.session.getAllTasks { tasks in - for task in tasks { - task.resume() - } - } - completionHandler() - } - - + if identifier == "com.yourApp.backgroundDownload" { + DownloadManager.shared.session = SessionManager("com.yourApp.backgroundDownload", configuration: .init()) + DownloadManager.shared.session.completionHandler = completionHandler + } + } //设置播放器会话状态 private func setAudioSupport(){ diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Contents.json b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Contents.json new file mode 100644 index 0000000..3ba3072 --- /dev/null +++ b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_1597880530@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_1597880530@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Group_1597880530@2x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Group_1597880530@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..43cfe36b9422c90bf7d3d1bf228c7a47c3fbc67b GIT binary patch literal 1426 zcmV;D1#S9?P)Px)Oi4sRRA@uhnoX!xRTRhn3miCT(4c`23K9yE3HF&T(zuVf6{f_6}^DWOcdGDTk z_ulKjzV`Y!rUA&^uatC`q?;vOFX?JYS4g@nw*OnwUy{y9`c={iNk`3$XT8X}u6Fm! z3x=MQv?UntvK2r+F907jvxP1jv<48;eqGWdl2&&)v|`P_l0K5OyRB9l18{ekeMQpC z6^t)R`dQL3Nhb?%^I_pJt67#(n5sskeRXnOsWOo?i})INm^{YQ_>+MTz5#__wIh9 zr1wfd&;THg%vwJafV*QZyRr>ncCVz@nyR>t`TTo#e^}D{5yAM1^LCgSXI9cw02Ry= z^0t}T(KbxA{jIwbg3&mBlIX>0ASwa4`+btW%r-bJ=?*jF@8}f%!TEQ`VQAw1Ds#F3 zKsfA=nay8R=96^XeN_l?Q#|7`++b#>atSU25XgU!gi}H1Vo__!2LssjDl) zH<@!2odB^EILZLtki;dG=pHlsw&UU4()(jAzH_MKKBOrKmA}xdX0{YqmH@c>8cBa- zrn9fO6`$^MC~Mq37si`r#^+At?wHZnv6z%&UAnPz0DC085I-W~tnCvjhXfD;!Kn!@ z&>l0}&OZ|X71G~sW&r9;BLKLjNF0B|rzD{T1iX*{kmI~Pl=O!-p(I6jKOEUFk0wG+ z0C199;)i5}0P00UYaoO|&h%X{cGAo?SloO#Z=##bjBwS9MgoBOz-x*~a{wT(=jw(| zo7vi4oX6rZ0E89}LRNIRhokYg-MR}%{>89*Bo zBIHhxH=wR2G+&pwy8GEEJI|qkC3Q%;)SP&}97cO5ahb^tmQLr|j*9V@cy(e4&;WV zXikaUhi3L%R}~DYfoS@E7Q7&R3i`=-YRtw~9-bfX(VZho5@hIGLDh||&;ruO&X}Y? zm=IyLYip59XR$7~nWp6_t;LObfVq^KyO_=m<#c~qd!4BaBc((awL=|}=ls-}Zr)zc zqEg=M;xRKYYFkC4}WO zVN8l>K&!7e6`c`kXWIJtw330w*19G`Er5tlj9F=ZrNKvHddx8^%vYATxx^X)$U^M2 zK{r34n~?_H9LzS8>#PRGTL-}+ZaIR+%N(VilCuhHGpwihU2|z zP&V|UCCRG;INbxKh|$I;&>Am$9O#;NwbnotcTp7KfS3la+Yk!TRQ|dR1H<1XVP3qY ga=h*92~}tR2gJaV9>OT_v;Y7A07*qoM6N<$f-*3ivH$=8 literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Group_1597880530@3x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Add_'logo.imageset/Group_1597880530@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6bffb8d2092892864d7015f7aabf4628fead15e6 GIT binary patch literal 2795 zcmVPx^lG{v_%aBXDPKH7T)jMw~iIiJXZ=p$QDnh7S za;sdzL`F5`o(dVMXv#H_OD<(dNxFEuxTNKL-e>K{es<^g{LcA3=RCiY$C`Qmc;Dao z{q{cJz4qE`eb-vsv!{lMACO8U20}4 zC;Yv!U+V=RcV9!&D4-q`yk~W2KcJlJq$6Zn`|i%R*+tSp zk{%;zzHI~m--8C2btq zRkt`8@8c_NJj%?_($>-%pjh2!RxAD{Nn4H>5n#TI;gs}EGeb*y5qIZgTo!*@j2PtW zfF$Yswzg>!fRS{(uh#tE1YiCw~Yf2;{eO##|FwoSPr z4EC1F8T>?_-s~iRy8B%vJ*6_>AC0i41nEFCCuzINKA~Aj z2!e4H&K(u_&IizgtEe0Q<>|#~h34#qOP?IO!6P)|eBzCt62WttGQQe3uveq}q*{Q& z5j#@S8;X}lEjcPX-&XE^@5*pKB*wd{4;LY}+ZnOl@^^oyq{GaN(|C@I0%-dJo|_kqG+_!e$^pW(wa01A*^Q~`wP zNL)CYv!9u<{Z`2=Kw&8H3vwn9!~9$`gTLubrU(#Z+-D?hlf4{UebYqW<^Zw(pagRH z5=^0)L2mUXQv`_jhtE1j5^hG4S(CB~pwJ%n?6mwmiO~ zNO9bHmjM7otoDn{Si@oCxE>8a_kpg6bWIKhhN>68r$l@J0FfYomWAg|g7IDfKqU5X zu{X#*gf4w_@2V36OdFusX5{kTpS?4l{W1W0qNEE7D}G!R-Qp81iVOrG4yX$wAIE#c zr*8p3xZ4D8xxA#0hk(O#Z0>U z%LCNe{9heFGah7vy!xF0Ak4?p%qUI;bQgswLxj4Z%$&##c6w6S9WZgzfFx0?J;RGjtpX2}l!Z3Zj*b2+d z>?=*SM;FfwWQ*0&KDz)=8RqlOB+vHg;^mwC#y|j~7?kq7MBdK??e1O|=HsG2w8_hL z@ytK~f|G`uPBKfFLAkQUb}n9a%f9nr+3`9Ay5sColq*pzWiCf7VbY%B3DV9f!}?#+ zo>g>r>YXW;%AmEp+6V9Xwb>53I?7=#OnEVogO`44_7K$$>`SUa|CbYo6l{=M ze^mDFKZu;*$~^VFLeg=C9coN{EW}t(%ScB~5Y3^QF7IL6-OLEu=M*mxpwW4gm-@n~ zEHg7wv%Sd_0iq6*=9;{PfvoDfcPL&6TSTqi!9~#~*P4`QQu|2iJ8QuXvGA!SrB)J`(kV5W`tU(-OP+0w>dIjAvs4rW%{{b zs9$MjxnXNFzmd;|+Nb)YR!m^i2}B_|MXI!@=Ryp2a9thd|636xVz6CXwIYNq0z8^o zFlKNix!l>BS?JTC3zB4H-poR_gB;+uM`>Y`b|@IOGV96jk$I)qvGeWLbjvh(F18iq zAMH>%I16is$_ICM!VprjWkVgO&7qXi4zQt)mIJQDz?RaVkqbuoAT#^OIFOrm90Ck= zjI?EzAPu!8ldCa^vr4BIa1NsnuCtcRut@+zO>r5}^6`+GdY=^bhCa8H08EO)wql`A zXaWvjYNSEMcEcvot^hqy_kA2n_?3&BPGX#zkNZ6O+i(%MHw20XXLruDD?bN=yk~E1 z;c2w&VgbUDXN;IUEk(3zLV};ur+tbr1z>in@&42F=qiNL=>KAY%msjbL|ykRT=q06 zBICbR+mZ`L;8y6ThpAhzgvPLi7@v>`?Wwz(f<`&=euNd*PD1>(-eSdeV^Ul;LL>jU z%T4$y{ziGZ3EnxEn$%rxLa3AexV?&YfhL!m01TZ1X!+@Nt(Hw!X+Z#G+YZWr8(C0w z%8K*$>$4= literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Contents.json b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Contents.json new file mode 100644 index 0000000..cb8014d --- /dev/null +++ b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Gradient-09@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Gradient-09@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Gradient-09@2x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Gradient-09@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8dc1516ef9ffbd02766ee3b7236bbd3d8018c0 GIT binary patch literal 88886 zcmb?i`8yQe7q+jVl2l?Ul}hN7YRWKEgd|&%tdk^35i)}@Gj>U#kYpL6?0d2dGlBw3HItmPbGyry&a*nF-W>$z!P>WOWH z;X>|tYP5u|(LS=1!|+F$3A-a(S^Msq4>4D7lD~@l2Wi7_Ca4Iiiz+h@8W?GD9ub6SbpX zqgdqov8Gz>43@AW zRN?w14v*m1QO|pqJC#WqpCl(7vOE19cL$1`LXLa(d?w$KCWlph+^H>JT%OR zXRl{o0zXY+93^r$S)YE4rd&bJ23_|x+x+N&!|aMY-2gvr%x&Da<^7R;;>YwoJoGE) z*8Q@e{?kM=^u@LbsK)lnflZra%KicIwb;duvh;v@GIQ>i^+3V#GY32HD~`ZJJV)Sl z!gIA;Mi?XXS-Zhlk9q-ld|~nhyf*``rgT)xeyk1QxN`KNLj$Uq?K;-8AU{A4_pQGx z<%C10EBBXmh2^MOJ!Va^fZ)vehE@sUk4Sbq&dfb z!t1bQvDBk~+a+)ipNluj2IlsU*sX|wE-FuPbhS4X>1FAqPs14+^6z}BORduPFbP4b z!3e1Q$=OU3i!h5XR8fQr@9kH`df}zk9mOZ<%!g%Er{R~3sHQUwEf<0~N3yimk^<92 z{zzT{fA3Ukst${@Z1-z9Nt$EVsm|C}P!n_R$pz^KTwd`%XFc;yawbF8?a9p-FB1Ws z783m5l^_)J)L}k8f|2Plt-5HAb(Y2YGoQ34&K#XV#`?`TWQlz+&JN$mi@03l=vNhz z@}|w*CAG2%XboTPuI)@f)05que;nL$H<|thJ`rCo%tPHhLTtP7SU05@v;autZ+*f= zlR6dG;1QLDdeI0;sS;IEXH6_e`kd3Q<4p6i3ryzLi$pK=!NmVwsduVVh;ECh+?CAh zW}}}@Nm2qoIM{cY$)AZ1RE2z3)%#m8qj32#OJoNyHvJwgKy)w?N%?){sX=~IK*!g- zHoklk@)nm@J{dcCG-wwtgFVHMwB+e)w3MDpG16M}Dpy}y7}hn9+02Ov?6hWFsgAtv z7$+@gerL(WCCP2^Le5KbdcsYOmBAd_x>^gT7Y#;wn)HKOV=}=$r8E~G{=Kt~6Q=KX z+O}FQ&(Ke4M^HY;aV@6|PPPmy>BFA-`SJ@MG_9;#2wgUkaK z8+VRDNP!PpsO=&!eb83-z2!3pukW14X?o}z5hfYyzP7yuiJ}Fu(P0};iaSl zvPJv~6Z@eLCT7M!1$D&B`HWBNX?;kmwX3xq7c8`3rpM$i5t94x9sae~=M@*G2L+hX z$6v3ZpX|Y7c4aUrJ%`)Y<#c5O9Fimn0q)WAi?`wSb%`a`strf!x34g4jWY9$pP^3H zNH?cTq;~vFGC0dIHy{z<5|uxTfrC%RYVV(@I%DCNZ8QG$jXBe2xYg&6^6o?Psr&7= zYXw-}i=K7&#imXxc!lUaZToj8S@_4Y#{=~1KFWtgOScbbChhO8vqnYEvbM7EMbAfN z8@!qThO~C~JNPHS#XqOqJ)$p--|*zz>HT39qZT5cHX7khN%d&?65dcM9iyMQX?E1* zE3rB!mR(H!`PX8iH-quky(0;d z#LFj$xy%>(_aA6VS#&RdRux1%`QefEA#zINruMkdcI9j1|Af;emt#mri>43^aoy7$ zi=U1rw~@;@2B}#!NwqC9wjB|ZT$CSYOBc9tQEt2C$4<*`jcQhpE=KSL;tA9@^n~Jw zhla)??ZmRsNsLNI>sHh#+C!H7cBV0*K^!aJg)ks)sI-)PyFdtWZ>6sJ9)O=pPs&{W z20Q7LVEbb$IZddZ*6TEvE)ZLZJF#~c|4e{%tIJ;~X=?K4mq4)ylV-gqs50BPbdZ(B z0S%%^fRuNVSfzvw7Svnd+{9CS3K1E5#P6=Z==kN;H|WO{%9Eofw+tR-AD1ZD|0xWv z<+6TCnV(tCTAt}m(4IX7a|xE)<=yXUuzN&K+{_f-WeyZ3TK6XFnrB(;V_i-BJGRU# z?AFHK<2LFCNxw-(8w~-v=l2av>qG5;?0FM`@fq=EGqfHt80?>EnFR z*_-)_qxfDe--Ac`!G|-}F=63(EyLO3Gv`I6wme@XsiQ7MjEg2VU;f7W2oZxH#@0EV zUtX%qa^t@rN~Xq$_HyT>A5^6zZ|UlhY!+Vnx9Qd@EnWJ3a4)l9F*3FON@Jcvp@+wh zlhs%9J~~JYo|RL&O1(UBxi>+G@HtIf=$heCn51@1V9S5-ZVpl!aZS%prE*4$rpn8L z$6gV93;hqGnDxuBdbR%XwS7jrqWk2&;cI!nRWZ-d#&6sUp%HE$Ik+cmgPPdxww11r z<=*4E*4^53j(;2v?MPZqUxWkpp1dx&e@9ug;Vf{^>8IAhR^y>#-5l+;+xhBcrSS({ zds1mJzUoiRg+-yK3hg*klA}Obs-gnKNilR<{$xq6o4CqtfR?FHq)KOVV)G8_yqpMD z*w$WCoHx8H4iX1@a){C{>5tF{XyHVKO)ZOrQfFv4r3LtjqMqtON#1tNx_-B?w zza~7$YFn1_J7J&LoT2%_R^;U@Om-;R;RAeSc<8PzKPpo*q1IJ{7c5oxo zcsz_2aL%j!qF55x=gRqT*X2g7ra-g{kz`OP<6m z;0R;^+cXsjq)=sd=1#APj8fiSvvf+Jx>d_Y->cj%DwUvXtM(?zZ>pabvXO94onHKd zRgs22(^Gr~SqSsC6RPES%YsE5w*(&#S<9YVj`4Vw@mce$L@PK<%g1PX7Jbay7U_YU zX=vjauK+Fv+~axy0@Y^X33eGKYJJV?^4BxQcfZsb$6KI7-gw3iYJj0aqoGS~(i;-Y zcjc<1Zl{WCWlYthP+e=-h}s|F7v1Q38uXU;mGD)?*eS0&C^PJuv%Ze-K7J8Rt5ut; zIDYxB^%oC0P9g_wd1I)+G6?@vg;R=~K7kg`ZJ#~4`NQ8Uby@O5=DR(|1>BbbMJP*P z_fAbnJc6>CQHp@OJ|k6>8!%E8;=6P4mAU$!uZafB$>L! z_eVAzpMO&stzb$egPe>Ix%@v1gUM`%wI)t4GF7zlK_LH!WWuwj=&<7ts6(bSV<)-( z>@2|Cw-*<(nCe)n*~TO-c1)5U6N0fMzpMY65#}yn+ikWaxW}}98!tM0{CzL1{FkcU zL>TowxM|LDy6LKX#!aZq-f#z@!Fu?1cI?38iyzCXZeGU_St1Re2tL_C1zxCc{68P+ z#M>qXUkh)08*vntyY;rpB7pKU?FFRp@c288Go`tH-Lt(NVoi+ro;Xc+Ov)46IYY{^ z3$#Horr#}gF>#+ZG*Afk=9dy%Fz@D1tdqgpr-$VkJtw_S~A0OI! zIc}BSoqmdcxOz#Q_gOSZDB_CcwBI`c{y0CIMay@5@?e#BIB~AM8Qqi}204aq4?sVD zvmfMB5-d*@3Ho-!4s|~-ZBlq_5*BZy^_}NrBL-cd4<{1#=#GvxAfYUMJG z2SM~9SXe!SE$+Ph>Atr zmaITkc_KQ;1x5G12o4Z~+cNN{sE418_3X+0g_lduRs5)~dC92(MCo0CBxf}9?vT6d zV09Yb7%MxHMo&m0e1YAg#j;*}mAuh0@h0hpSNq-&(2%8R%;BPC{W~87I7bFfR;LQ( zphH>($jDf)ij3zLpENGgTA`msPq7Jj&tiCho3_9iJ_`b58DE01f4%GBg3_ z#O(R}X7zGPzStB;uflJkeOSyxmfL=HlH2Ge=03h4x%3_SHe%`cf+ksbwZJ0$0`kRu zl@RZ@#B|W`kQ z3y~SZ2}eA`5y!v+kTDfSb(+hj9bGFyXc={s*fbA8*H9;&WYs`wgydP(E0m@RyE_2#Rr3D*_T0K>X}%4*+u?qq^#Wns%`3e`fgJEKHZ!w9MbonnLGu)4n(Lw0XB0Z%DJe`}_j3$J0zm!|~@Iui%m9 zyPy+D{#$q-`-|5BT#<&NCAE{m@4%QBV`j@TGY%bTL+$z-qMPrpeek=9Qc;fxBTZCPAW=irNd^X;q8SS^b6;M=I z2gv2`CNWM*9%oOgP60wxwtKTI3IqJ>S;&b>gbd;V_7vXED7WFeec&F!1wRv_jT=OX$x@99y$1_7*0fZ%HLnAov*mmre4ZlmlqSy)K%V7x) zM*?;rC~@n+4Y^!&3PHKM_eV$}ed@;8m@56!pFSmpJMXFY1Uno$L*qWCGzUXJ$<4%n z5iIjyvGiv0Wuy}wwmrNtoCd$nHw&?WJ;?|>xIb%Bxa z9$odqL#&9s(f!5BV^FmsfN12ZXh1=_FkS1wz5H#6~5ZGRfk*E-``M<_6XNf zb}g$X5H%6p(1{O0{=pZfK2HBrojT2^_uVzEV1yr5eI|L_imFk79R%Em1kC(YT~pNw ze)&z$D@-NV>zn3dxgupvnI@Qx<;?7_lcWa#DH|bhbg<*92&^5tUv9>DCx>Eug10b* zMq~wBansI2hU#_q|TufZ2G+A#16>-p8-kT5?R|wOw{1t}m{hSIyK9 z=lLs}yGlLUW{RaPJU>Gh-clM7-)&<_G(K%IE3LU1Jb?7zNK2s^ch)Y8jTJy8g4#%S zkirp9vgFoLo$o~Z*gXlXl2aS+3ORh+b#do_BN$8zxKuk6x)Rd(5pQhHn{^WyR!j|1 zmXqcFVA*X{rm_YvWj^@#g2&X^EJ;6bUgnSWca~MZ8eP1Vmpg@?+y>_g$WtZzDmjxI zEs3!u|LtIZjR|h6N}>K6Q2P(Dre;98K7CR9iA4x#?hM`av2$R22g1XE@CEqB99%PU zkTv#%Xaz|4B76N{9{koa+UDq26Gb}!S(AY>VCJ=V65#pN)UB(;o_n5$kC2}RNet}? z`=Np^vVen}G=cbxap?Q$rfI23B>ok=#j)7y6J{SrpW59Zn>^_N6Ym;rldNe@1EEMG z^&i14HUm7%6;0o0;@E+o(OJEkC;0H>?Dx?+|7}xC3#bA?fMbzX%bF{p14WRMn%*Ddx*bHv zAQ!{06imq6VXYL$$AYnKLG6RVe`BZ2HXO3#1V91E*DLLE#ihALgZn;!pzQTKrrH6>ooq3Jb6$5TRt*ZGvoSFqW4-AvK) z%>^HDb6S0BKR^|H$#TU}_jPf)$LD(aL9K6E!PDn%bIk6Cya!yE#qlMWCKIRPFh`iJ z>|B%@IW1oi2A@&hbu-xLte?{YV=BQ%F zbAC(RKU|yg-#GW5 z38CAnX&E!%Z$#~Na=E>di~HH?l^;H4EaKcvI^59rs$@6l5@+n-M`VpAO*um4 z1h5yaGU7m!I}mhzTxt#E!aHp;lRNlaGBhTk8ZTSc3Le*!#t5j57@y5_KP1o81s$d3 z9rRC|%AM3ZMSPp`N71^}%Y8)cC;P!ahtwu_MUBV=)=O1;$2M9bP7?!c7YvtOmEI)O zqws?N$w9~O?P1Qb0G^-3x3P2ETf!A?$@g~yJswQE?`|<}Y^t5YZK=3Y{)~RjDEk9B zx$fpM*PDeMudA3f+=y!E%=n0Y$+*dmnu?_Tnvh`~-uhE#qVfoy7B9fHyh&{Wt!~Ik zLU|BjtU)|YiFK(hWu$riO|{|_=2%R^RYJg^-N)k?59;`iYgT4LN?d7T2>Nm}+IkB5 zON81WzZ%=$TQQ;hsO&i=`HiIT#yi%)WrQ>=B3SkbplQ>JGPhZ=pEY*b{7Ue0`ER&k zEk8Ve9zxZ70XZ&Ua9vh`o9Kglk%jLZnOM~*Ixb{F%^*~<{N6kvYh=YXE6yT*PX1|d zVMt14d-(!Kq}vH+7#3qrhXBukbu~Q{Biml%Jms{+X?^8efSabgwy=4$f@Ue=yS;c? zllpJ%5Uh21(CFTk2ySN!v;Yz_Vj;x09~soCWXEt^kP2AuzZoHleW7m-uycJe3EOw6 z&)~)xN1-a^&N-B(nqlw}x)|4zPPq>N{?yd-jVqRmoI!7rE(-6{jI<8U*esDHnt+@B z)=@e8-?A+1A>S?TWm2EIEQX&vBg8~rS@M5Mcc`@>9pCLn2swR$7>)JNG$sUtMDb#c zE{snB%?%oGVStR*^PJI|?owZ1#a3(F>=La=SMvoU;857sY{Q$z*V4uFB6I)2_g#tC zUGDvW9+>f;y81|bLYUkN>dFuy#3|M(ty#ygju*kIr`wx!nkX~cWKEVJ4c46jk7#^~ ze4kr1ri|D-Vj##aqZ8YzGap5rnbFg{zI6}&W#5zCM1X|aQrb;|c*twOz3EE!0T;!E zuKSb3rm~IRyFl8qF-p8bl_uA@KkNjfzIuV=(kIA!*Lc&bl4X;ri9~#IepW`UUbeLl zE5pp>UeF)v>Km;m2V6!NKS>eU`|$bhKSt3D0)7x$yBU-e^q2LrNr~EL*nQ^hi}*2u z!b;e6L74{Og?Lk2ddcT@!imi?dj59D8$i@~O}7>e4#_w-eIFr+xaoY?BWNBmWZEJ35KvP~ZBiD(zVB?rv6@Nz zvVjZ8_iW#i+LHD+As0;ssGwfK92I6>Zn;HuHdE6>?{9{|vDz{pOhYJ%NFndIh7c{w zOl<44=g%?RV&D8~s4Okd8DIXFu<8n&u37{`UL$P(^M=`WG*Y*B_!qLD-a{0K)|F2W zE0xQoJq->b=n0|kA_)MoZ9gH0jBUTq32U??{<)hyp6ZOA7ni5=TC{WVV6`O=Xt~=3c{9qxs zaI=5MN8*;}O>EV%#$%>6MI<}20$Fs;&jp@@FrZQb1e~HDTBTs8Mg6jZIhGVwz0MFK z7}LZlI~Xg~nB}Z$G(p`t6#Afi`I1u;gHB>MS3Q;pJ^p)E{aRP)+*GxB0&v5;*y4|l zFyv~2Z6k;tx+k<v?z}&^+jGJ#L+hmeWJ-w`YJXoDMF zI6}q=j0AOm0H+k&x$XsvRu-jp7tMMO^iyQ;KoQ8bMRT)97^|H*e|J)&^LXQV|18pR zz+>AAcqkn$Lqocr3^^hwf(p8H8HO}@2MbFI($j#z6xEtmb0;|%F9 z5H{Xa@WH&X?6R16^_X%s(wlF`Z z9r1DKn{29PtZP2fr+@1zrP6B-e3p1;MKZB=$t~>khPX}|ZwkrNHTE8ZsxtCb6vHk<@ z+WB+e^xnxvr?^-6jANmYg+_C7sZM|t7M}SPdGGa|ViMJJ4t#@l4(=(7m8~YKi4liT zXJ_O$3wOUS=!Tct1F=e5QzT`&kbjF(x^qV(+5Wrm3djKRmH3{oSWcNaC3tR$ZpzqU zX;%NI&&mYRreeGX2itPywfyC|$hyeBFNBW`>7<&Nx5uPuE#Bg2?Y0jySQ8 zmR;PfYQjB;Qd}_p+%U<_(2@PH1FvC~M7_UCfB*1%KSXqy6@^fNkc5$8Zdgmj})i9SbK~ZFYlB z08+~qyn<{;2!A-KH*Dv$S4y6=YSUwaM-VEv=^^#eVGIz|3~LQNI31a9KnU*^ra=<6 zSP9c3Rqu*eKhLx)#lxe{Jq>?1ng6CCLeo?{RcFtf9o>y>fn4CEZQl$X9cYf}SA=Qt z7iYo)9{OLUgzlL2nNhwI<;UMOBr&_s+RnAD1qPMgvB+sL&$cmwY??(3(lmn<>DtTG zDC8-&a%*W8P{I2NkN|g%@tVP@NAho^!54izF+a2-asdZymj0>sQs6zMK6pn#=t9`W z+}#F90`I@ZVL~A58Cw`Tr+7>33Ke19V2r(*DFf;|lHP~w)ltDVHS#2?@Ikzt&htOPM6hrmRPxWw7saRg z7xVVTz8+9?HD2ls(9 zKI|Emt6>L4eIe3IQ7q~m&Yhm0rOyanYW42Tj(UXh5x8q=hA@@dkaxVOd9%Eq_o*x!;7<7_f_yS#{UTtfnTjD;vb#bTcx&Jd< zO{xRfc6YByD}IUu+C!D%ez(P9xN^bC*0aoJ;G&QFSEED zAg(cIwJA!0es}L}qjc|8oP--YNnm=j;KpEKOo@4u_VyE)5?UM|{|r|DX;6E-PJYIU z@}4uJjBw5en5?OOXc zD$gtF^GUVlv{^r~w^P#}n70E*bU*U$_Tk_4q@g*e|!~cgmA5WI95KQ+ZzUz(J6p#gId1DTS(zl-n?y; zll=xm0CA1&n|XWf8N+T{C2%yfi@L^u`|HZQgxe8v6BkXE{mMasTxBw4C%M)(HrqPQ?HWFZlOF+R#{Miw2T1BxUE>m9~N3R03p4rGINOBB|l7Xsb<^a zaD|Hpn4hPIY3^2RyB=^IksyJ^I_081t50M+1{_5V1Z2`qIK#2lu4Q@jyR@{fw&nM{ zqNNa56&I@6*3&)Q6P)5e!N*M#X1L0?#iMI}S7Cz`2)T;CeD}Iy4|Nk1kyB$Z^_QF# zP?+f4-a?k1(DTeVX6SCkIUZu6_@9NZ+Ja(e#tzRs=U8S4K%1)@X9QNy*zjsR1!>UU zYzcaz>%AamPKdo7D8Tn$fhGrra)FT;bsti}bZS7*xG;VUoSxqMYtC#6&NsZoFCH)) zN-GUjup?DWeB3Vk1`}!=(0Po0xMW6gU%9h-@N63?gZD!m6Vx`Fofrs@B;~>8LwQ|OpO)qAgunK*$yXakIBb0r5&DqLqK8Je& zb1nmK8@L(V*Wyy-?bs*`-At>J`sW*r4p!O8n9yIpE<2x|%*g|GQ5|1`6sK}A`vZ8`|<%Z|Lax54LW zb!i??vF;?rWb8K{_IB#tbpOWC+o1bwP`Q`avQNTwmkhPQyD9{7Y@gwdKBs8-UXb^F zwY`7L{%rb)_EsHyBAhbLv}55rp-wu0eTdL4};I@{mxWG&Aa{4O_{ABJqyS2&`+5gRGu&|>DzL_5fwdWIw_l27i?1pP}se; z?XHN3fNG=e zpODBciIKY`i6pe>-p$X7$2x=rwHgX^it705fvTBmr!HSM8K)Eu>IpaehWtH!eliaW zW-1UZf)4B``Fqj*cTb>9AdjNaS>l{9=x=Z~MQWl2do)edh;$d_ReY;7(^%hlc)0OEG{aAZdm*u$H=|lFyG#!9 z$!zw%o<%8CsV^(8yY1Xubij>6Wu(V4x|cRD>V~FqB$eTGy;4ooTya&#SZYvp z>+sANm7m8C2RbnL5Zb?p8xI~rwr_>}62?6x{RG?}k%m>7uMIHr@(S0!zR`7=#4J5v z7~8{F-7)lQb}1RH*2EGriIFAOe>pEu3)E7YC?1Q2@j|PzdRwCXe`^J(!AT%kw+^5u zu#k<^fgjd6yIpL90GpJxlp^B>2Qm8T3S1tm4Wx4IikBM!j@ z%@#^=(p&XHJiQsnWW9G78*vfDmv#!EqP8%58)>Q**1nKOlXi^oSlh**Vz{bG zxpvR(EE&=GVqT_^ah#@5tzV-&G+c+9OXLrsS(LJM({3N1D9yQ*GVk-N-tQsxMA<8h8T*D9oDzr!k3~AQyM|eeSZ&wpvvYyZGCN9k zq|)V7m(xVRUWk2<r4<>X# z18jvFIGPgqi6@)P&hw>hOn*UpqPD18yp0X#YqO{mJ5Haxw9Rx|sg(ioh;Nz=E=WZ^ ztq<8u?6T= z6_b=uZOhTR^b9GCCA(!(3QFs%7w(mGl&L^nU3uv@U(MT9XxX@Y!iX3I*vo1tA4O*F z^75hFJJ6qO7rfujbkb*IySSZW`Meagwm843-pLN`^D2-X8p^B=XAk!c?W*3CDT(&?)~pKAR3fc|tD?A|Yh z6RB#x3T41JOtSk{=-{z<<1=s6(`69FpUo7Aan$;oc#~Xfi{AmUjnGHXNgVIC-HkxA zz>P#!^40Rameo;yS^m?MvWTE$jtq-2MqN1`IPEhgxhD+E(3+t1O8YC{%1F6D&IOpE zgD(vFW$GY=wGe`Y-)rXUl&Y{SKD^ksJ9ydrNTVDb$0vZDv0P_w5rhI%9@(p$T~j01{ltSScu)l~Zx3QQaaqvk@|isMopl5~{d3?J-`ds%s)+VG zKb5zhQSJ0W9z+vHl(NsuJlw4f7zw;a+Icc&lHS?%e8oMnw1eQnbqtL5a3=FkW&tNW z^z60#wv@X8j(Wk6sSwHl`;pXy-(d{PE>LVbtELd!*{;X9(8PIIk_AVqFm+F*yA(&3t~4D`D=`H zb)^5z1JXeP_jn*=^2<6((;t%ZO`P2X_}A@Il*>6bCQ`rI9uD;yc3gUIRt-i18i{c_^}$WLfX8CxlV; zp6Ti>PDIT+is=EaTlh2TzMb9M?}by{;Droqj0LPnQ-9GVktsjKri8UxvxDzXJY@Ga zGlrnt=+Z+wKs}pER^@G62Z9gU$~;VgQhXG^uzB)lPPiYU(nvfXA#zO%J@e$Ua--p8h*ShyO_XndtCOaVXDlS*ZW=C?;NAa0A~%k zzOA!WFypN=Z(<12rB_KEkP>`g!& zB6QIhyKb!je?lp|VS`e=$p{*QdHdHm^#N|zCmJuo&3Q?5@F23v?@PvBq7#7Ix23DN zDwWae)(}Pi;`pJ#xZw!22V0|O0a#H=QSX$U`d6y9sP*0lu7Xxf55)jCeYagu;ej!r zBZI%%oVwyB7=H^(geI>IbAv3@RDlK{U^NyidFQM_LS*h}vCe*cS7wWIz`mGCn(xVR}+(b?FAmp+HOw0z_gMSC3RKPuQezCEz}V7F>G z0~ioh;el09`G8;ZNuGZh1LT*`#4-2MFRZG&%$tYvn}!nis((5yJwsKsX0EA_u?OMR zB(uW-m~|&bpZ3UwofSJ&RpVw(>_m!5;@~$I)-gBks#d=o+Ff}YqxET#AMsw##hG8% z)7iO}(w~xLI%HF)H`wYqsWn3=8fwhV5MEd5E0VE^_bHY4_E}!_AI*m}?;MYGCt|{M z9%1yhMRnqg{bk7?8C8+QVdEa_>Ckk4v;<24V1kc=Ys)P^LDdH>&z}g`!kurVa#Z$g z_C#z(_F@RdWp~(7F~9(pPR&Hks%a|}`J{HU`EUG5V{9>4=X<&0 za&yD&i~+k{yHBMPm|kawb`R#HGAOEum=ReIuCLshDqBI0s|?s$Y0g_SX7aDdZt2}x1zVH58{ab?Niy<5MZLAZIf6duUJpoH zKH0qzQtbxcJ5*++#x>S1Gfatjj=sDe=G0uYlImvRZ`^kjhWT7ywX3jaI6eNRi@mVD zlWii-mDzSJZDFR*G_n(|E##Nw7M%WK$!VcQSz0uNY5h*d_jX?a`;Z~EMx^2xm-R<+ zeMo$=q=%P%{-u`lndaXu+_fk9Be!|>N4zWvax9qS)I|hW9$9TPH=wxdk&-^BYeZU! z1r5t=63&5I=fB~6Zk#4RXKSW3c7hT!+bgYD-TA63cUZ+YM$?Sdb-z&-KEkI=7l*HJ z$?`ga2Y0^)C#mSEAr97z#V*UtdcSNEcDq<~Gh2SgzzZv9#p!(*>Zn1#L|~H)hijulym#B$?oB=T-|Z)P?do6Gms0MK>er#)kDCz%P4HlPG`Z4}rR@b$7N9-n;Exrn8KIR`8_`V_0 z5b0cGzCPe@4FUNdw};)*uzu)NAhQih1srGo@yZLxrH2Qe1I{&mtWg9G+(GNl{lH^Tp7{w<)tp^XAKK zx=kQ|^Fv$rCgRT_e8Ae2d{6~qHWgk3qN(q8FzaO5oksM;55Cl7%*(E$(t*$PTG;|~eO^|O z#q!aGlJx`d>D~kIb}#HLi1FB~S|iebcwc?$gugn@s;m{#YFUlCyY?#1sibP7LYOVw zW`e`3G(&K~XhqwhrqK^}3=&w9&&tjF>eTr1Bi-m>mQ3{02AsJxgkdR_P_9Vb;- z7UA;2zxvz+^s6*dai(j`f={rP(KMVZ(XcjLoL@*0k7pfs z%k$AjUzi|rtjp2*qp5HYW-#hgZg_*UmH!gg2ltHYF#XoF4G!ET(;UZBltomIn`sR{;a+L)+FD_Ka4H~5J?l(BUO{^{!2yGOwX_DO%k z7~&M1koByBY&*b4%bxyDxueu{B(;}KX?Zh$Sh=PK=>Y&cif@qzmEi7O<~3HoQw}g535jwH zVDX4Q%422yjN@>)mmbs{tFIeJzv2R1)J!QZ`UY2E>$}|z^a_N(N{Ie3XV&P6I|{G% z9l;46#$V$PMzf$beUM;t$3Ww0kb0dVx5Ys0^Auj_=Q2Haox1qeFv~w0wo}@H@tM=7 zB?sI>(F>Ow9Y=sMEO}(rs(X{|bTQj8gwPNHCkHeQ2qUg!R6|$UL%n$~WbT z{9dHiD1~^xuQCHh>OI#gm*)r+ABH1z_k-8KZqkITM5*ymE=^uXv>2@IvfnO4A+VczqZvZL&}Thn2f3Da(h{w98wQDx)Y) z-P%Uyv0+S?1>6cNDHP%Mn>U zva0(P=eO9#l-G)fu^;_ZTaHJSuef`1Zgir|2LOTKgTF*csb!0L`p&b2cc z&m=C(ZrpDcxcbACtZ?Jg#L|gPi+?rR3LQf#)3UIx<8o)Y<3(P!{mAml zv>=zbN~P^YM)2}0=*#$fpg3bt_58VdaVMh^tR%#EhFBLD)NU|TwJ3;R;N}my8lkT& zT0gEybJfRXW%fI);YA?B9oAD_(Whu}sAFO^;G`DyEXjilAF%-6B#7k85vNGd-P|g^S>GQG1_HKtK*7-)P7X(p|brbDm%k<^iP`KcPQPt zoN#<;1zW>?I`r>*@p1$&eNC1j$kT-;;}f(-v|-2a_NzH$pWL42^?-X6uFJ6BI-Utz zl*B?z)=VkDqB*QmY$#HVpM6m*AwJZGQNOO*-32Zg4!+ZLVZwyd_Y|z&3y2jrU>A6C zJ+pX1CVSD_{;jK+Z{B^IiC^WWLo!v-2S@^?!ab_w5wtep4>ST%fNdqJeTHoxpO9h8 z|7i8{fmCc@9Y)Nqd!3@^-{^CgsNT7};^?iSj5uGP3Q;4s1$W0A)_dFgi11dN817a9 zguVYHwK~_CL%pM>kg@-2w=6N)a=86THHEKtuK%#K_XmMAl3X23y|7SWI)- zsEQq`!9z;xwtRQ3*vA>!U>tTw@oV1G7Ne=U{k8g?5!1qO@#i&7XB@?`*Xv zF&r6MUlRS{AS!sDo7q19xqkmKz&>Qk&&AX02mXsMhF=|_yY9RauD?V8Q0UavHR#X# z+d*fFHHS+xIi5(ZNNV_{uf4b~!{*o>>B9n_1%e1}*`rwZSF9>H_l5@~6`2}C%@9jZ zhRlYls1ZPWz!^enUAIb?4BWi*Gl@*z~<4U-+%wur=7=c-R}v zK2ubbS2SWWs`<@Ju<@f;{+2Eg4Wf#;2}oqn@&5L&H+!>Q_?m5fY)>6A4|hEpNr zog7UoN+B9ptOf^izR{eiQvY0$<%K-w&maPnawR|JmBkRwVl?z@ul5?$#33q#>d&wv zx1P0~8JF3qmcGd!XZPs7muj(Z3pge1wfNJ1(d*4oxKBuO;sB%P2X)t1if7*f$;omZ`GomZ`GwY9Bw{Pp|) z{^Ne#ulsx7*Y~>KpZ7Nu6b3@E{GAscu2AfHof+Pn-Hx+j-HdTIpxY!>d&+|bV>EBr zMni}5A*ZvC)0M1ke|%;WR6ePK;K!6X{=vbU>jPObPWL$B$$QCP8@_6Lx5A(9GsAhr ziK)72LOPGMto@?eX?=6?R688rDanq)O6$b;c$hG0`R#z75RTZDdJ|J$p9)s5=56hlx~O_AcUchitQC`ICmQ_QUrZ;k%xyzuU)AkcR& z{im&yQMbC-?M{JVGtZZHeHo0&Yw}9?M5^4hRx)jCMPmQt4*t+jl?-o=8fdHj)ORB| zwm>Zi)92iaj{9q|=uk-Pp{&!nrn-|Fz)U6fs{V=F$^CxX`5mfrbno>2tnw5X6OUOl z=?nyi%#%XT*Q_Q>cTbZq_9ETLka?fSp|t1xz~z|Y_rW84KgG6KKw3Uw$QG5437dyV z|CtxuWmz`_f8S2r<~EejTfnJWP_f!W__xy2-6tqp_Cl~%?SYl_nmEY-^b>bC{{Rum zcnZBD%0T=BSwX0V!28!w=T*--niF{sW&2J4=7up_k*Cx>bpca-NfK9s#8TJ(@b(Kt z21~y_(_ETo3ptzXN$kMOA;smrIg3*-;<>C*$OoP{!eFAHX1GfBt1G(>cPn}}z-3CY zp)?^%hi(LZ3W9Nb?GA+b&=we3Km~R8Ob=0aX#dQ5$v4IR-X_JNBSWLNvK5nLot%y7 z%NH!g?_ttx0L5eaW%igj^qfDAsHMhP_L}#+RxyusCZOAPMdV$ zY^G6=qN9u2qS)wEfV`VsK@qyTlPy5k{^C;5=PBkq%Bv`wz7ohm!1`Kn9C^hzqW-X> z(>bz|KX3O}7_#)1Dc(+1*-54bdT0+jq!0GJ2^01)gYJFlaV_S)4|$wc^uBtGnTb!Y z7{!+pL4YH|UKr`%bfm;o8hdQ0NCf_u^3DDu@3_Pm^+d4LI@mK=xj^Jc5+j(yTc7>N zf|uffdGiuI$sUvGe9;553dHsP_2GXpchDgP;6y7k8Hyw(Zi{-J&jjvmA+)ttp{$0I2yONzb=sLiUJOh)-q%)nxYd-sww-vRd&|VF|+WbkY6?mFU z*AvwnVq*Lb85K2XXm|Px{rYPXsMS+mSPjiwDEDsM;SYL{$9XFP(BrSw+r3`r|B$66&4>k zH_G`0p33)I!lG3w<-E&6dxcXGq1Z^=8{7b~0LE8$77%&5-s|CU0d6R%9#>~9OL2ZN ze9RP`TSR24ie8_S_hc6o`*i2=YaH*2^Jf?ldDQWPV@;@k&1SU7)e!UN)FE7I-Dj7m z<-+6e;eFGLeLdOpnM9qalO_qifH8xGeWkV|jeiyQzK67I6LET_bGZlNqNmb;qv&+! zo`d?TeS(A$gM~}pv=8GKChu>{HxDgR#3E?Ayj$v%GAq_sp8+>^zwEPfl~<7>(9J0+ zcj*yb!Qg)zE#1qHjeQ?|G82m;S-ml_s=mAIyB>&VX}sUXfJ6IG1`E^Y#!-`mdgjrg z$oYEV8mceAVleF0v4fY6;Jwl17dT!{y;X-a$zo98S0?s{eEnD?Uvzu&8`H{ctg4`a zJE)^Jk*-tSexd_ApjG4`L{6v#-eT(LP;7OTmLfjXNPNcOHE(?wwVY@^Jy<-w1(GK| z?!-`iS(q*9#l3P+m<`?9*2I4h&Qtwud!cw0YCE^ZQqcGe{Sk@~+8tsl>huP$Nh#K@ zoidl$?1WpT-TFzgL)y-;EQ|j(|Bhmc|CV<=_muj5fo%SDbQL>tF;sRze4jsm^qBam zqto8ze{HgxM*sXS*3WR<#o--TWkV`j)V+GjDcHJz3Ra!FHz$iN_?$Ow!7nqV!O~| zogaDYQ%Y=3wLhSYuHVnMAETwHdjFVr%)0+gGR>xGokU#8mUoY*H!B?s& zZOG#xNx)F3g+iiCPdt26LwpWPar)}wa2y(AAIm`NPe$5|G0wA)dNUo7aOaYU98X5k zbLuzLABR1i5x$M4EsffRjZfa<>YZ;i{?KzO;1!MyAdaR+79^|g#2$m-{4JIHVm_O? zNOnKs>E7vxL{*4tiUQcdMmh0JL2t?IEtO%TF&f(nrh6{|Q_-Z;ees!N#u3>T+|^D<%-B7F^|T*RRPlOW zZnRxr&>r`Bj~*Gto$qzDhl$%tmNWv$EQ?M^{Efh>uVeUFQq@){+PQ^>zFCW5=-Vo zFGPvIU=B{`i$nnivICeTRqCf|{pyYLK~R%|ZrLztd2T*Kc1@x??9*LTUlh(N5zD{Q z23=XhD&HZvwleOrtvo|MMZ3JvrTkdI34JjmE0?IUikoMVX?|56vPBP`t4A;lBzJ*q zc+~PP;*GPxLJ#zpw6=b58e@4GbfeAO93Ze1-vP@oqJn+G#!b?5izy9L+5`PJZ z=ivcercVI;3`wT7H3qW1n&`e;n3BuOCIzQEp@klNaO59l3jV_ZLbr_VJ9_nx;)qxG zF8ZwyVvJn@#>LsA^Vsfw3j_fjc64*2oiGoSPzbF$Vorkw0)_s z+i^*Y%qM_eGx*%$w-D8N7TgQ-B*-kXQ=b3Ksgcr!=dnKu|7N?cRW$1T1hv@T3*>SQ z{;Ds3QYZF1=#5)4yxbH;wx;Bg+59O&bCV{urF;zgXCd7+=lzquVGBk%%DEr~8aOfc z=$l}t(2C+1I+LQuK0*aHP(*y^hWe?3WV#V4m5tONKbHlIBkmjW8Y{{$20np2klqI` zd?XT}bLkVHr@{KL30*n=HJeZzvyz3UZXj+|?lz=M0(~N!L{{z*aS_)bERIv$e2T1V zhj&5rK_gLtW)*@3`l@D4<_0~UH&3h5X~&{IXQPF{ZG1bu z{v{kfx}M6b5D+!{dtq_&F$qRIlb?u}q;91l={Y#KO}N%9-HWc!#otxg7&+O2t~Dmj zARdQq*JrMFxDNFwUmW&@P%J{LbBtz@0?Wx|0R5?GzzF}`LdpC;h^$w&hAhC}uZf)w-E~*k^EyK56u-@H8I#HmFx*j7%212G$wOf8kvby!+1oO2Wwp*6Pp1)>J!h zh1#X}*OBHXE31)y##{K6M1{xs?;CpO{KxXU{LWU>8;>q(BX1Zb?EbF2xc6(hFi4qM zs_MVv?CN}uHn136dz6^k7Jj*AmYv_6dW!KL)pKaB6AphL`P-^j+d+Q~X=1Q#(a$^v z60i}_=Dz=?-aPobZcr?qO&2HJHsU>$7sbema^I*Y{^GJ|cB3mPojLUeQT~fJe?hJQ zt43Fhu_KB|uk)vTp{G>GM90Ak(QeYKpaCtg_GV-~R1v)1oL2VF!lR+GM-1!j=nu)jX1Y>O}4?z`WgD$IUnzkKEg*JboKoo3k;XV_gk(E zq1U|ft6aTbw$9Exk5xDqlr62AZFPJGZ#DRbdR^AGEpRC)%KDO-A~T69QV?=!SCxdN z{CWe;RZnleCB?$zYgLBh#D(;_h-!V zff)?c3)4KvT&>*FuIkTuwYWpq9!1~w>Ln6nc9!B#t^OpmoOy}BeM|qEc^h$OX~!3R z7utwED24fFPu&>H=3hTVuM)36muPX(y4A;V%Q?KwJB=c_!|iX}WvcP5-cCM&jW zi+26%Bu+Px3w{a=NT*rh1o0B~=_-V25K-!VZ8pNE>c!DTEtu<|zBvpZs(DvA|Mcdn z^$taN^Q`8=I&B5-fG4BQTGS-JS*E^C)a;x~zlYkV$V>|P>=3aZt6LMo5oc!^&CZbY z9vz_1tO=*WCpW{JQM(+Tc`-uE(yS+U`p~o{b1mu<2*T3s4Mc%F+jl5qNow}Yk!LyN z*HlO-%^R32XO_KKg|hifOlQQ*0YpaHh`hO@{WqrLh+@y~kb{KAgr(~o!q$ah&t|8; zETFm$AZiu+26FX5{Zi=r$RP4PqluB5B|_(C@S|sIv^N^ivChw5PhnGs5cs~}x1v42i z%Jr$n2IW2asl(@aT5ocq|LcyiIY*VfM~%7A3bUT?X+Dcs4@!XcAK+f&&e!rqz;_GF z`xy?3HIAHyw+E;FQo#3JoS^G%-&0j9sQnk=E%rtF6X0v(u=A)QMTouKGAi6yK)DG!w9U}t2OVq2u4_LxZLw*z|QIm=}Y z79ptDx)+3|>UV#b54Y-2_u{?w7EE%gg1blGqX|HB4DpU5b!}A+LX!zWA?6AS*x{hw zs%q#d_Fv2`_OQ=I*sHc}$i0m(+nqjhe3-Bgh$B#*mA_`U!o5K?%X2w>-(l50#S{PZ zj&|$@$EI9g(=lk}%vXg&KTonw`XD>A4SI4;7j3lgkQ|S-81X#%k>>rAA~UXaj#5;G zJxulp_$#}ny5}%p8Fl%Q+u}x9)#mm{^pCumSM~{bwnt~}zY*GJ1}kUpIpT@B575PR zvie|8=uVT~Vogb_8?9AVW8LjPmtr_V932&~_x!HWW=|H0I!aJdkhy*fhEW?DNc_!t zApB@Fl=V^gSdbwUU|z!R6W4*(KtlBDt>$Cxz9)njXCPh)-@~8kbHm6hHQMg$I7;cdV6b=efV_VU|(Geg@> zQ$&kf^pm3T%jiV=C1f$O&dkrbPKtRvhu)bhS5^CS*i{J}`xM*7z`Bs?&mnoo%_kn5 z>s)XJ>V`14a8~qLeI?4!zHD#Ud2jd@z}jfM7C|u)21EsTr-PC1OUS_*1wFtweRy`J zFHf))&L<_73m<@4yJiX278k!L%3V~h)#C=@=x24f<#G(o9aM1d;jtdUq{@!2m8t5vHr(FUfWmq z9ErAcDxn6;fZ6oppR<|N>)!i@|7pQJTtr}3QKjVzpUd4Bnpu5ygsKEj)$Lf@vAfY9 z_z%rGLwkzL#ov@>z5dJ3u&}D=v?^U`z<;ho*d6?Xi8a{kjNP-h)Y-kY33FaBpeVK0 zp23LrII{io>nr>g zr`HU)_&Mcx)n%*lHw18gDYZW%TvRP~H*glkvcu=-wR%L|7|2%Og7Ao-%rpm!QLaRd zna1iY#M~%Y_+&<^G(~%fzRDd|0F5qVF&WUYJFH=F1@M5!eJ`*Md)iqXYC0(HlO7(cz6A^2?pESOx=KIcDKL>(3~2?NPnN=wDmxa1Tc9 z0tC?uH6<;~#son~hN7Zvo_dZntRrvF9d=d5RrXc~(dyeXFbSj{)8^qTpMnhza?dj% zrQzoA-S{BQo8~9IyXWF?DQL-%v}oTC zoU4>&;rwLwnHB<&O<9SaSMsb^VOEQ1F^j!Sa?qKOkdkNo67G2-4|pH2@POA{y=Rb? z95xcSe>w~La7)>vaO}sBl{tSyjk9ELKF7&>72}ZS4xd%#O|FC3L$l9KVVb_|;;9db z+v-)S`RNgFsS$rxSmh$y8 zt;DZJPgwGU7prS~+J1|BfH9nO+->Io?11w01Y-=<(o&3#vI~m{V zn?b*zt}_}11u3d_s7E6ktDCu?Z{~Z@q|auZi?us|@z=HHVE>IHKaHk1BiZB9+jR-^ zZZp@SD9cc5QTvdL{!9#vrZ9wQ78ZB(fi6jL|G@kIQ55GrjtjRTjIf9oE=AgL;Z&a3dPg$0%TD5J7P-{$m=HRP$L`Ya@xgVZ(#W}T)>zX$neY<11Y4d*p9w}Jp3DC7r z_>i{@q=&j(d>wnT*k78rzm_K7sQVGouPplx*>1Iz#USf_QZG02tTtRG+bsX#foopf zm%d-9ywcvJyYTS|B$CTEoD3UmepJvozHBk(F@s0YzwaP|jOa8zO57ZYiq>Pts}IHH*l#oQ*@ce1T`GXlJBX$+Qr;vOepGzPo=j-12B{5w*UKi@$Bz1P?5{ejc> zR9Cm=J#-?xzqJ@*ti0clvlTlWA6LJ=*}vPbSd9N!DcBlih^u4#l?>VM?d`F1w-anP z?UQs@Xiki-S0s+SCZ0Cy)vi67!w%3_T2#=eEkHsa{n$3~T6HYKJ;~Lw(H);O?y3UI}m5a#QrzC(YYbP z4w}n(>n82{8`gdp0R;CS@)=k^i<@w}omp;{a3@1yD0bt1ag6L)*PS;PIeK1||A(~O ztl(#GQLqC_c-bB1C3tGureD#vaq@B$Prjxbv`(prGZ8M+1Q)#OggIy{mA4UC&@V@Q z!36oDGU4vc`m*U)q;~h8^jfYB_Vwb%>B~qLby~dmxT8MXv)^srtE!Ng*m|trvC|C3 zt^Z;pJtQIT&;U=XT$exWDzTGVrOU+~Rg1&3t>XruOPF|Ig1unTV>d5w#*GzgD_lpb z9xTHf4FRc@Q=8#S5`oK-_tC;3G{f^z2N8P}VKnl_s@G;_C#cH1y+{U+z#N?gY?8yj z?kgqRGVSgt55UB4HvzJ}+Lk*@6Tp`vpTgCcddg$h!H{`y@8cD~ zN_`ppgH_w*sE=y#BVW#O_4c2Yv*nxByGj!xE?beztVf*_B0htyKB**<-#o!(uW3wA z#!A)r-d?-4C`qw!Z25?KMw&0QV zrr1SU*zqpTUd*&zN$0nksf+rIVD9=2qQAIMZ`n}LA;y{r<)v8^qlO-PF~@%)srxw_ zu)O-fa*C*Iyd{3*RP?ii6IOdjEmxYll7r+%zsqW0Efk(oh}YIJ9=H5szZHb+F?Ad< zo7q8oC%nQAdP-UDE`H8#feQR<6bpzAvkx68ZcHuGm+ZbZW@9CSV^`%mVs#tdMX5-b z5x$KySw`qyPliGA1A9-^-qvWPGPJz0yU?pOyKGeY5m})DRp6+4@OFT%bSFUT2>`t4 zW3Vs^59CBr@F+7Lks+syuTaG`Qnv3;s{D{f$&u4fvKJdR(f`zp8xuANbvIdZwO^E%+K%R2^YV~N6pgFHZ9AOAdT)=*lz(7n7XOtxAuA zPv3~VpdjF4O!{j1t63$v-44xGV(*K+;6z6GSEI2qk`#VB{X!D$40C*8^unHyr3JS> zCg53Q6ZnhmiZ456VcB_680c2rL zWRPtt_TsJ!qtx~?+M#l3npz8WLw&A6W+SOseiSy^T#<1jWG&Kp19ow*J?-qLAd80$ z#DK33qj$3w;F{ZB@4=EB>^9DW0}E3ur3Mc(5iB|kPhgw*0e?eZ29CEjF)e;kcZB+3 z)U6|C=1m1nObPCpDkcxRFi&t_Y1O$#W$=2J=q(JSu9LJ-1DA%(=aHStr1l=-9mr=5 zuqD|(!>tboi8Q8}P#?=r6$8c`%VvlNU~zMF_FO4m=^ceCsRJ31IoG09+ux|SioF*y z%lgJ%Ut+Lwwvx~XI?tj&Q%672r?M8Ox7=KF(B3(YbgKp4#n@I&&IsNFH5#V^9(s*g z1hu?2V)LvP;~vtLo3P3mQW(qmebquKZ z#Z?`TmX&gve+2|O{fjCB$=0B@O2;M8UsPi+jESb#Mr){!roN@()8qB3u?8=wq=VAtIq6` zO)@-j`@T9Xa84k<3;IASBc04vlI%5SX6%i_uBlPotgkM_;+%1--V)(orEw6}`P>xl zx+6A}w;d029g3~=H~X00_$wAo*pGV)*br*dR>yO5lkL8%NDNy_dub_m*U3&jXjF*Y zz~=XhkFKfoH|fianj)O*DKrU{!S5kXX8Y8w7$Eb|)WPS4g_~~nOFN_|$yY}g#o)!5 zrJi?JpqFa@c}>xg;cuk=ti{KfEeR3Gi4$vaIwy04hT-)raSjEy^vkC_D(CKiG-A)N zDtTLBdXoyF7|OoP0J>Gi?8R_OC`RV$;uWEE6`Gn!X7 zKjZL{htrB>*OyYp{{b({WM$J}IQ-CL*-k|;f$%64k85ab05Q9BgB*qPkd%-s{pew$ z?Zs&5t=dLMyhCsHWJ9y_Q+MvRu4zUaR=@P*pgAfAuyDFURo0vh;1f+Nc`=XEikqhg z&-3ug-Gb;y=F|7UN!q5IRLw-`Er&{#(w(wa8lGY1ymk(YKEeYSON5yG(qD27g2Vr& z$5YJdH_mET1FE-Z`+H*}BQvdvvM)Bd|{ z)yj5M51?9ZRhH>YdigjWCKvFc9>omiYw^X=18Ms-k zjbQ9bVtmmeod%aQt75x>I#VQjI?RZjuHL!4dM4q%W1~t&d4QMm+2d z6>ve&*Fj@t-h<0uwvwVog_7iQa4`Dt38IFfy?CCy5|WSze=Bb#!;IC91)tgJnm3A= zIM#O*s{@`*#jcx;b^N9Nf3qW*>_OasBLiHb(W@V2uZDPFo<&^Ote6x<*S_^wmadAveEEcEN)#)It zZx|7`0KDCr#L&+_sP{+lDb$lDPh8oREHkCjVxvZjw106NI*UD{cA;08aU9Y$6{XbU z9&~#Xe9CNG@njds$G))%)9eS zz;EJf=pd;_m0)>k0oX!1*!aPG1yFaT@-~_q(|3AQ|J`M{xjt8$gh!T{OV|zjN92R! zUJsO;{t!xS53S^`j&}6vH;`V~PKW6{Vy#W%oHyh;pQSH^vA}-ILDiOZj?BSDEnHVEFzLO$m_KS$i60lA}+1fkXO6PEl(ODgJ1>7%}gbnh6ezmU?NLtp8@Sh$o#cMn?OU zFf_C-9{Pk?uWMLleUJyGF!HR2Hi&`X?&AE1^oZNq;m&LX_4{*^m_$!n>vey(_L)&s}(<6U~VVP=o|6rU4cpLsg|s z;ag;^XRf1IadldREV#_z(E&}UStARlV~96Lsaay@Z;CArMe3lY|#AYtNjc=y~Mu81^EK*QIxT#MWwoEa?87jH{I zrGIR0ZV(H+?2|$e_f3J8?f{qKU43`DYbWz?qCH<5*K4~X%ZJ7=pQ)WratvYz-Zrd( z;R84_WVussZp#wY(cgBsorvM2ddTrngjNy^aIR~y?$lK<9y?uxy~}>Wo&-fJ)N{}c z>OV~9%8S#U0~h3gt~ydUZ9ngA1c7wJwLg{EsT1`Z-x8Vt8=NHO3`G2q?HIvR^SDO; zX4iE*T4!q16s0fSy>OG)zUYtus<$f9jRW8wqQpk)Bl20I+;=6 zNA(u3BQKE8k>hbyA%`WL)1JU=&827QJvv3CCk(9&jq@rE_M+2n$~lF2t|umk;!X_i zzAOuatW*v!c(1AjHD~G?A?>5)!C8Q-MD~Lm1fzqfH;KNg+EgoOln5Lb_SCuoe%I&U zsMP<#OcI}B)XrU?=pG%;{iH&S_6F6{Rd1@o`JxXXOPiFIM{2kP8kUT`KZ)l;|Jprak`( z8(DF!$K)hAKX|^eQph^rIe!0sJA8-JL*2+_oOn2&>Ns_x2>4!N&C+#v`1~lwaE3N% z;_&mU;_hB-=^xJq9weHI$)fGW;E*cE0EbY(n(HZ0FZ0w5zlZ`*1SUR-bZ7RwaTJHHrRz z*o}Ki>*2RB9&3e&>Sz>OlH46BM8(5zJHI3e*J~abLD{UcHnf_APCD8xx7BWQ6H;#C z4V7>E8@wi`JGI8KQu*&;9Nv2R56r{Kb%>rn8;c1G?#>f%FSA-1aBDij!Na^kDo$_0vg1Sm`i8AMkWwg>Mgm+404C_bUQ%+fA_^&I)n z1c!5uj2)o(8V#N{E+B7gUSVcj5Q4rkWG|LvV56%D2r+&4+8( zYj-(iblB%Efhx*ap8%Xu7yJz{y)a>!$QvI3mf(%gdYcZ;&{P> zDnHEM?!X~cPd3#n1G;MB5jAhlG|0n_0S;ITE4fVIqf z!)WsR42ueB>6AVGH;sn&yZgyfI74EsP*6{yVqU z(Pdql=vi^_Kk~C(nN8KljEd$gU!2M`bmiBJt+Z*Pv?e zz7^b}_esizmvPgOA2^DNfHBky+Wh-4$GuTbvZ&qem+-NS1Y*7*{o-1VSN#&(u3z_> zOK}~voRkyG=}!iyg`X^hJwL^Xaj##afb-6m=`cd4V92(Rllg7%cdetF7(TZ=1IW*G zu3pZ$BJf(?d^}ZGp8PRR{@m?&Tb04ws-z`qVJcG;8E2kguO)9QtBkcDD zEVtC1ye1xY+9Tb@2QXO9UbDof2x{D;&6Hn}8y0`qUCwpvj5-nY>u&A|sxgC-K~OX} zZ|-ajw!TKotYW37GEV+Tk*l-F^`GRrn~ z1s&!@^~Y1bH~CDXm9eA0Y{z9ii$gv>Kf++}Sz*b)*xHIxKLUm4!M zWd9s4oR!}zmE~2W=isLGvJ`8h5m0SPDZ6`!7v%fN)=p4jmp+4mOc;k z5IVuS52uITDoc?tYXsr}$(dfeu);b|EUsCOxip3H4VZqSE*r=cTiqsTRSK81eTDXy zT*{9%aDSFDo;5!Pf0$kTZU`}id(s`K_2qbJ?>u^?L$@$f>}(u@xdN|YzvE9h3m(4; zoI1|9pFSO&dw?bH3wEgfY88%lz0D-q;2^!*~n1Ij_0d4|t4ut z+GqXovqB|GcqlMdc@bwy)n(2qkU!Zf| zjYhX}0x~99Z=L^9Zs#cf;JX(4T}u?zxKs&<`ubzzWgB*}`3z>fnDc1I4CdN^lQ1XB zDKU;-xj=^?o}21FRSf+-G}B6brrybpt*i^^`&~iqc;ILJB;Do}ugqg^C0)OCWBonC zWWIQ-jG`MCwHJumUW89m*iU*Au$v^H(Cf3iMjWnXB|+bZXB9i70NwhN$M*Qz&i$~n z2oV+atx}fX!_J$~R@6LPEx22I!KF3^0hSM^mt&+~(!KNnG~E!|E&;tzkR(l#aEUXk z<>8TLlN)83KD640bUBMW+E*?t6yJ!lCFs6{-fuUgnA}{mlm_PhZW>(`nTId;<235L zqF~s2(cZKDZUPu5-AnEjR@D@3D*cJdjcSfo8#ZM=V7K;A-UZ(_-SwiY*5B!6j-wWd zp{m%XiyJGj?MVpt4bjPWNC7-v4f^t9Cur!O&Vuep$T?(bhgq%Jm*Nh=bWi1ZdB*M~ z!yFMSKWe{8(u7)tJpjWQTn2^3FTN|*vM(%n8-BkNXFVqSIQSNyj<*txl$8;MUhm|X zaWpsY2&50ydKrG#6t%3#iK*Apr}dv@x5JEf@alKKjllXOSz8B+BReE$#Qm1ty^WCe zX_RiHbI5`9dJtcWAbUQ@g&-Q;J-b|@qBq0hV2Ir*ezxn5JB#5{ z1y5Bo^~|q`q+rzc?^b^avYo(*MSdL4V7li%EVn)zjVlA(%5DlN6mBV0M%5S&v!$bF zC!I{f&!?u)BhyCDm7L}D5HU@Tq5`zOX zyGL^X?e(L znn{rrKHAPm8*NCgWU<%J5J$$1f<`A#4WFR^wdencFj7Z4G@4eRaub!>zkGYwJcXPRtv9lV3hs?P4fHOj_LRl~UD{pNvX)@h#A6lw`)Q zmR(c!T13u!xY$y<5wYGOAAYxncLPY4MRO{=oid7q>=b6YY?nkqyCOifuNT)$^a`J` z;zxH*G~DuO=RE?t_FgFS0D1ok(z!>?c$7P@Tktkov3lBDDsI=CjSdq%$Sh-8P#NDY z1#3xwJ?0}HD^ORZ-vqz0Y`p&2^UP2iF}GSQr{1;8C_6bmO~bUyGkYDGtjzg??6iOx?O9i0kJ6z< zhIMCTu8m4&G`}EzJE}ZG%n|9q;{3oH6>ov*sfrbpV)u=bw_!qg{I?6vpDq&9;I;Ud ztXE9)$P>y9PYM<`NB=$sIn4KZ4>;`~7fe{fNkg1#B>L~+XvHiarL9(om!>N!<- zEQW^$_szX)#>8N%RB7)qnSLRm)i!DAE~=fGi~(5$)|Xv5bwld3i<5lpJMZUUQIrLF zlh^!=<9=xW-T9&}^F}gVA0M6kn;DQv+wU~ZuS%9~;N0Gwcgss|$@enV`09lHEHip{`jKq^kho8d(wGG&?>~o%U_E^I%Yu(zvdRT#8(Us6-!o{0di-Ef z73a9~MdeokUEBUz+y~47&j5PfOrydddZB~xjo579{4(H;EC<@~B!DC7Jnr=ktnU*a zsZAiGPMelC_NG>2QVJDTecPfFv4XzAcun*`jHJwp(gE*FgI#jvPi48EQHTN>D8tvn zlOK*{%(i0_gX+j6?yaGO9Y#*ME-lFYk*zgJjRyoe29DPyx^sd4)DNxwF|jlF-%>8G+)1G){)ZhpITT$eg+N*1=>n75x%ld+;Xi5 z({{ukrykz5|0YN~Z{)1HzG{j%pNFJB#wUYrDF1$UI}TJPhdWk(CE2_7_~cPrE{9-j zcm)HrVQIbM?u*IX67Jf;qgp9$jM(xzzyPmi*2}=1pXGDK>2mFM+Yy(@EE2{tgdiEY z5eO5q`&dR8Y__R#8mCsePy77C)vff=w*@I6rCZ~$6P}>3$RVWd*cIpUK z9D2V1y**}P@JjHZ>U?(1R;ao=>MrKoUsFUO36g@YROj|Z8*hmaXY(+rKq%ka9C3wd zbwjXf+w!rJX|E9Z-ibESHMH#fU@i@fJu3d-d=7P~x{6)^PBIc>_KRIE(frw6^@Qk~ z<3!n^V{r8KsU7W>BBu`VU6y~%1`-SWRQSV)_m#8*`ci%?&=Z>a6Gy+zlH)BV(6M-ns8lP){?deieyuc^7?SgO z;)@wth-^mwak$5OBH)d(?@}uAl1D>s(OQab4+0|Eu>%KP$os1O7K1zTrUWG7ennDr z9I(Y_<>1k}I&dA4x9tuXfZwFBkp8K4E|Hcz_F~%S{s$3!wI72DQxySc^=HBgh_1Z> zrGT#cXRC`jC&)K!#|NVBI?GgMh^=}{$6-6o7S5R*yx-=f2MIaJ)}r0!C^}`%w-`iT z>ldC)GCU`kT4~1%)>PrWhTzZHjs8R2qaK``C>`p2k)^QVc$~l0iilRy7F8m|w?YW+(Afrez))fBmb%o&%Ya9k`bs0>mn(@&@`fj{3=RlZ)IF>vH3i?DYr;2Iueic ziN{+wJ0m>lhd)g1S6@AW9ZyJWEjod4 z3%aPkoV5aqKg+^BXD#(9TsavL3DJWsi$QJ3+jvIn0Oeb5Sl3GSD(QdN_fbGQkU==_1x31@Pz5_sP)FSwA=vO* zu@BC&0KWWg6jAuiX6BOzzuIeNIoe_%=!^}04FK!w>OD?OufQ=e6<<;PYt%31(x&l0 z!s`obSb=o_n!NFmCvBO%EY8fG^3hgF33`I*$^S_bU5(P#pW7-k>of#? zmtDf2wH3YGS{(G0h2Ij4)s|4))02s~ts&YvQD>m+hS{ zE%a8sLq3*iwd2~9kL!6G5%kFn$y#!BwE3uncOu&6n`5C??c2DZ^DoE@v$`I|x8|`x zxi7xSv*v*1{ILvp#(8pRaCv{#&S+PJB7wGIV|eZiu#|ML1L@I_@6p&)9LRPTt)Q9` z1EHsXl#Hq`r5H^Y$W4C*4IDZ&Qjqe9sq>;5bE}l`?#5RgnCj$CLcRO*?4jD_cmpcp zSU~qwLNXb}(wz@w;VwsAss6%wSQncWzD^Pz1pPhcN#o2a|2!yLI|Gq;;>mx6ZsTW{ zD4O|iq+?uPbUL}Y`wi`^*VseHEYyGPsy`%a;*wy{eeDdegvC~SSr?bL~2*KQSPhcd#lOa zO!A2JUA4*2WYPVmaH(VgUZ@CsrefC2&?mAwW1`oPzbvU&kx9-U&Ag%BW87p&kK-v8 zG5Es;8?W>O&YR_F-EJJOYj9IunfUQSRls1${27ef!UHp~mu7eQNBDUXhAc+N%p|+H z{z?P6e%V+aK2vl8Tp8GR7R_guaP4{d)xV=(i=E{=4nv*>pS2yk#{a;tb1$4M<#YQB z86)x8)T9M*g<$lNdobwMoi|ac(BxjT>qf%#kCV%hP zO`dsj1<{N=tBn00Md$v{^#8x{N>V90yi0}c(@_yBA%{^(Dj`V}CP^wGhZ$yDrP2wN zvgBMu4x97XY*Ui+$Y~Bc$Z2e|nb~HWZQp(Wg4Zw4+v{<_{gv&)iq96Fz(>}|@AUl} zNkBC+tkBh+4?u`G_}Zn`J+K=EX?%+XJ)g5hQtvFMtWLXA!E@p^`-IX)q^^f0+k^GM zFPgE*w$Mg2mR$jSz4Rk$F!!e$8=*Ozv?k#bF{@1W#0zAf9|<0T>BLc*#79^k8F8u~ zr1paT?RkTXbRFLm5zP&CzczZ=*#V6toMbus7P9vUO(ZEe2dQO#v)_hS9?&eAAFkgMc+ zStUYyW%(G}_XyYN1I54-0spPZ0GyVey4+7j=VnQcJjpFm6y@u1b3{x-ZfhGp$Fe!B z?}z*yUbR}f0;ROa!=?Xi%{8}keuOM1un6A6o**}BX9{uIzOxt^4cE2>F~(X(iuxXTIO8GD+vT2`52 z{kGN6BQ=jkHOA_QHbWnu4L?FU()$Fk zpgMnbxyPLI3QdZcYAhfej4PG)y%6~^3%TyX{}$7QcmJj>)uyujPlx@&Bv|lo^7FL$ zs}wJBf$4+$qj=-B6A7nwunbaFH%jCiiN-PIb$95(Yq{ukRi?z}T~X8(lr~CHX-U>J(APxFW$V?$i-*&d@5L=_qF&S=+EM>1%$POrb~fxg8*Ly>T@4N>ePmq}_SQOt19V4{mc0{l zU&|u-Tg8lnG!-4VNnw270J1gALC;nHqJ7nTeSgIMm|)XEG%m*w{@D{A+sctoE|4>c zUp65A4S8*7+(GX(imF5p*wkw*Cw%300E8)qJx+w|@RbwmdI*T>N#(X5&}i^4VpafR z@zYt#*Ri+#X27K&@18qwd41hSgOP-i_haL)RO238`&LFproft&x*P9i3kWO&D|M*; zY_twPp8io42 zm_n~CcobrH$rUzQwSf49Dj*d{85UJ?+PF=#sHBdXEa-q<{Aa^le-iuqHi5rrMg% z#(CuRMAIHJw_ns)Ob^BWPGT2Jt>e+hEvIu9AZPqaz+-lU2Z8y>ZA0%i^`6Li^y;1z z5g##U+IW;R6yF~=jlXS`Q1p;l%w(brxq3ImUcR4V534}J1)a}}*)M-^<4=G;>u{1T z4rwE?OE9v`O1LIxSO+XtD1PG9NG2!wE~ZoFvyXb{X-e1zlAunAlZ~8~5WZs~{+Wc= ztKEWks|dXtVmIJsR-)sp%ba?H(x7r4(xU3nner)Hp8rp-Dfby)_^6~&HTr!=L8e#F=JZn)zFQN z-{Ge|RhYKkXjS`R-&gM&UI(^W5Poy8r?pE@&R&gpG$d=+DZ}H2S~}-CGJ$QcR2e)) z|1G16LEm-QFkk*>L)F*{zO3N<8xydxTVn|?v*9H5*t22}b{OSCT#fAwtn5mVyhl&d zSHj1&rUtCh@;r+fLA(&hV*D`;56+w0qd(Qd2N%;cw=+I zRrJoZE9mi~A;M-xzHM=o@NTH3_>asu5)h<(6&R`&tL6z@7{c!IWJ-mH^zi&q+14AR!H#C@q(0#S!U(5@99rJ@ z$aG@GXx!5rQ*(!^bCThappNK^d&#?*drsCHG22BL^%Q<U0IAb5l^Y`?R#BIprX|?I!9b z6a=J60&fWezE8{zcEV&o>q(InHJOo@w@EU^^7BiV=mm_|s{ITZ2P3&B+(pJx;%=h8 z=&~H+?4{pYG&L2v@XnQXZ?dk{qQq(ZO@w6BYR?N$&JwGah5(Y4IBKt zvUQoXE{gmd`x~xvZ{l4-_Zk1-#Wg-S#rICj!~WIN>j1r!Mq_q(;LJ{efsG5!c+N`H z6o{7fr~N?w0uJ*YKh%J$SV#5`Qgz23*N0kNm7ient4vuJG1tVC%LP&4u(gU?5tvlZ zt{u_u?b6}(&L(S3&u4W8zuCA@!+oXFe~Z` znVj>Rab>6ILXr}M_S>P`+=NL|Xjbq77`kwM+#0=EX01*OKO)gh41?%&z6&d$7F=}W|#E(1L9h_EsbV`eWqb_uT7 zh8{;;p7|SX1=g39d)uo@+UB4liH|yb3jQ#&2RnkhJ7msW`Mb}F_>w%X1L^NfS;cmg zc)?(-z+?P9G4Mf6_&LNIqb|J6)iqRhny+^LNWAH<=MC%pa@qjtMojfMuc6S@463&5 zNV9*BPwI_(7$D^wdh&~>?fwU(~=vlNXO^V@>_8jsTOn7NPvwMTEPY;!)OwxUyW=5c z3BCu}i;psE;0gbIy1}a9#-Wa$&z&0bf(5v5e#2MBgb&CTXGNb7nEf4EQx*po9BvrK zcH_bxzsOFCSsy%M&MQykomf0%7XCc|`0!L*-+f>7_Q$z5e;%Eow8Ve8vDlya$Sxy# zFY@fb{7v1k@!S~s{Q||25YqwA;~|PWb(`%;sqtkFqoTy4 zaX0~I9aoBPjEzv3Ixd=nE>D1qi!h*yd8cpW{6FZ9RG+I1Tr^pgAv+}g6qmI8TC%sm z^!!jWbOQG%P4VyRiN*^PIuhDf`nfyvJ`NWGYm<=-PEvfv&aveSC1IZ^2LabF+x`e= z_Zck_#DvZB$qPe&u(bba$3rc&|KT?<8-w(4yJueD2lPY3HHP4RFTGgpu+U+~@~yr0 z>KxXU>F}H=a^OUeTH|L(lJ>$(y`JeqJI3l$;VW9dtF<2}+sBJ0tv3oUKqLz{;b7$s zHhR?m6~Ve@_5_iBlD7pJJ(ri$5!M86ft(-}SJ}Oed;r~is(X;KQwY2+a~LtXhia08 zl(nH}!aE&D)F0xYjES%*?0fN6(a-HYNhv*fjMdgtrI5G}n|n89pFtHJP@O(f)yz_f z+5mweFLKiX3_GTasy!v&{AbV>&a5z!Vs|gSfR74Je46}wB(3@&-7(FMv^e4OQ$zMt zG9&$)vmouz2BeLK9F!b1hi57ODz0#yobfIC6UV7pjT?11t(R@x-ROz&0SKP=wFpek z;*FRyS#M6dcDo_$ebHf0Lxrz1S8r{dHM5_e_ajYfZCPyeU8&P*+{aLZX^cCVmIQHE z)uWsb4f%nW0vhw98emRnG9>t8jdlZfVIHnCGp`pd%7S?H0iuV&_Stz21Mp-w`Uz2y zq~9D>V=!BzxJtPDmj;>(7Dl9ph+?zgF!^(@=@zyAu+da3CDXMps>mdGx7K;Z^*~d{ z)G`b=53@D(Hrk;)yQ;{iDUq~m+V5Nu@(FEbe_%NfvjqvC)Xh6b$A?MbmTMt{DWENM zD-=$(ZoaP(Z;t6aWBIr&`uGLDyClar9K<`8>h|I*y+4PA*mLuU>nZNik#wct#oVK%a)z@B~(NvL)PpNAl8OEd+P zK&$`6oBKDn74lAo_5Z6dXPd`h?c6}0;O09UFO*vG(=0t{rBObL4GyJWMj+=wS{>^U zq8xwi`4$8TR2)8Uhbp$=64HRT*GbRw-#luFzE{6}EqqOdRWEWGerAlq+1zTt9^;m#NWkhSqli}}uUiKQB@Njr%lT7~y(@KoMTwgD%y9Ss7En4%EK=azOgf@)?E7*)5m%dF)@Eg#ZEp5;F9HWVMht7V2 z+(XuQU@Wbnobch*2_7lfE#b39oYjgVCvTlWw-{2wbYbRGfmg=#pPj{Su=}vQVkKid`sk~1NCu*IuDeq;jBPcuEpsTB?)kIfbTQEs>Y-y{4>;2t~A z)}dBv%ztp$Y()DuD9Mo#cc#&Gg0|m&dHg+dgtcy#^WRE2_r=aapkBNch<||ol~X`> z&D(DC7DJd%JUUjCv4^CqsAfVh-;Q#2aCBqw~;e00d?x_|jZ?AfkL=^n-|abwQy zx^M9-QvacBdTALRswsPh`$Sme&hWhb3)gwMLkR)(U+ui0j02KAFw6&xv437yF~|mk zQmlKtblYcr2SK88?R~K3ZOlAnBjl6|$`eT11sN`V%aWB$&_)ggB+-3B(W&dK?K4<@PO>*j=NL{5x|Gdx7 zo5bWVnL99g)5Xb_wr$2(eBc<0{)F-}kuu0jqTPu=+}@ zQ7mQBkKk1I4fyuc8>Kd&uFGal*}7{|J&jkP3R^H{1{mqIuoK32j88@mk`1v)U8@-?F6`4Ir{QL@!{QW6qF!juVoE1LfcJ4<+;LTgq zo3pfr$#p!nfbedc?>p{b-Ys98GUxhygtH+%MO8XvZ^IC0_&xj8jsNNQ1t8LKx1EM5 z$F%1kO4=t=B;~C5uPK7=&J@QFidYR|NB_1o09oJ9tdCwgZp1R9EHAT_;b)Q-Qd?3s>hQcpn<2?`eAd0jQ@kvRc zD}(8cAf}H(=a_)3^0KTFv>aaec$ph?YYd`R!Aly}xZLQiBYv9@+nN`_tYO?76Kq)6 zsFz0ZlQ`i0=pvgwAUu9)tExxMw-M?mjen0Wl%MB44t--avEvaM938*+(YkDRX5_k9 z(95uWrcCX3rhiE18Jn$x30M1R%c*AxM+JXk7i#da)DvN;*JBJw$-giiJEpWJajM$4 zn(*bLO=^Dq%9`I5CCCjd#+q;0KB+pf7SlW9WMAb)g+zRWe?UY9SvPXhFJ1AQ!;26? zn2=%3_)qrTwhvnezQps5ok73FH`g_|RS!^J=&Sas2CG`@?^3gEJJEp=DM3|=Wk3va z0WE7UGny#M)9=kD1cNUi<%VC}qu{$ru_R@{66w#oQAOa3m|FEkorkd($l4g|bM(z| z*~0S8GqQ0?*Vc|uPXpLhaThV6p#-6&d{A?7;eqb`ROHAr7*%q(HD+r8|L;bU#t$a8 z&DsqDFFwB!yBOuLd}e`+JUt$vWkLR21T~VMg@^QyeH3Qk?9Y#@SU&sgT{2xKi*cI^ z^fl!btB{$^fZkByhbAv~jMsz+3jQTBl5{M~wE1P#h^6N8r29{U1@D&_%-1H&)RF0> zXl|0%b;2#=jyWfKnEo*KAj=W=H}?)b zO&lA&>J$kcaQF9I+RgtDx*ag9W?gsomx^4c&B0S0Y}-}OeAn8Bl%k)h+ht(%T6x6J z3cP3)NtBnka&d&@IIfSw%DdYIh%XmqlwGYZI`kaT`sGfFGPhQ7KO!@3Le6X0@nXYy zP2&@>0cj&|)ro5o|7Ph1E(E;@w{U$cUjdRaEWwWwbo$gq6Y~3;G~*FTb?n@{JAGt{ zeiI1vropHi?@L}^2Se9*_$THvOQy^4eQx}^XQ@{ay#Kf)0t*})ZjEwh=vb_gv1#>| z2j@N8ozbPemY){?x}8x3PI|Qm zlpF~1+vUGT77+i`Y8$x&_{UGDO=E_dko&3T>0P#PD31M#8`8mlJYy`~A&>MhhyR8V zxrQptWAI=9?pVnG{4kzWorB!{EPCu!;BGf7fH@+e>srFn+2v!b?0{4+7a#PJGYx{9 zP(qC%+7rX{NV*?ndc=j40&nGmVlMf5_27(4^y0*?x-xH>)(b`Y?{%i%nHZcf4YiyO zki!`#<_<8pW)hI8gK2Q!eR0qip~L4Q(S{}Xo(&D|_G3X1`vvKdo*L#ppTC-SM=s3p z0psmIhk@h)y(;6%f@S5$=Lv3L;R=anEsR+WKoX>Q^D_uu}yegPA-{zApj_FJ3LA=tOK(0$e zzEcil=`4}r0i4wUJqI>&R9N0AqNN-(Cn}r|ivvsIsPIyJfqX}hfe?5|?&QS!!F_W{ zRzJ5p!E9&lb~tuA4NW0!m`gR@n4o;q(vZl!}2#}m>=>WH}hdw zOxgp4Dfe4&qKnd?z-(c#y(X|IkU6dkG4a${oY1rUuICXLC$1uGmzeCV*Wu%?)>ol? z(%>2S-23)#3##Wh4IY+E$tjfQDZzq$lezKH+7yE$0TbKD}m4^RZV`uT1wn zsHa#2BW@(-ai7YQZh6f-;(A?0JN5^y=IhroRx_SKT{A+ToBtx#tI5-J_9}zk81gqH zY*%Uz5dwua3W~S*MHer7AXFN0h4`0}$%SMN^t9)y?7n4MRM+nM%QlZu$zAxg!(K}j z3NBr+n+RvO0^ke{;X0gipQ*=e7_Hf^D(rr0iBq3Bb2N0cK?Qn; z%S2W;5s$_QX;8BKbF~7( z#3-*S)&H%d1a<0wh-2AcF>UFTYbYu;&*uHQhTj%EmA#lXGta&b=q${fXFPJ7HCh~? zZ#|_N%k`-qxW|+>XR`1Qjy?{EK=urqhGl~+=fB1$1-LP_3>iR^THzYnJDz5={Y$_7 z3NsdfH3&5Y3Nko{Z+^p=K320_9{%@4?*rnrebH0YnBV|+ZbyZ8_bIwN5bdqA2(!efq&e*R%ZZ3r zX#cAnW+M6Jq0amO%f%NfoY{jjv7B^yV3BcqaLekfc3$#f`&o}35Z~V8r%}Jh0zX;f zKflWTEGpz4EQz0bn+Y!OxB8Ag6wW^ykXnP%SQs`@7_BTXevKj4f6vt_Q^^I@ZFTN3D?A#fjzVM&=mZPU;9{8&&o9!$y|0DtQCZ|q9jX)Y;NgD|EDcY z-t9EyCLpVFX3f#W{foEkEy%(=6=cRuyq^=nVHStgF;NQ# ztJ9CYwKT7_RL&WS*wmPoP1sh*G=u2O>pgl{oiDBz{0n_kAC9~;TaV~$EH^auZw~f# zldgN{R`tuOxLHZ=NgMQ=2SA}~tTdi|v2kvNlLF`XUNVPE-1i%vT<`xE_B>2^w^fu_~!KI-`&DND;0O%uEw#+wfm3Z?5 zSK1cD@!2E%q+#IQj<8Xt>ON_^0}saOT|Qo1MC{BObEOfO)Q_;P`TbQ&Gp0QVSy4s8 z#U9ch%81UWna!?5HJZNA8c%Ad@Ns*yWPdl|U*nh!S;;kMLR{rAILkWycJIx2M?v5h z+ZNpWoX0*{T;jHsbKhaQUfr~)N(+Evaj5Q)DqfL%%_`hu9ier2GM1~NgMTIWh9~0q zxq3SVr(NeZm+}4#1T}Eu^iM#BP1y3U&GFNqM9bOl_#Q~>^M^9gc%NG1E36A}qH_uR z#|-MN1gbF{$JA^x*ae=cIoh;a;~I-193C=u(rPhxA>5vj?TAJy2PuE;cZOn>m(CFX zm(WbcLHpAN8fGT1Q~RxLmB)H-9bLSrz?NfOZV z;nFD_@?31NRdC;N)1{mL<4P_(58o)rpUrFnpX)H54c!X{C_-tSq)FL%K6I1KUp3j& zS4_8ehr_;3x!Q%ek&WjuyXH0=R1VW|#(jzh_pvxeUDYbNMTOv1Xz75%^3LPf-A8m5 ze5{Q5F==@EJczZqDy5~a7&kgp#vcAu;WHp!~zIUf4oYPg=NgmsmE7 zk$?<8p(HHX@{=R>h8&wgXu3Eh{HzNN7B=Ow9P+_5&wP4)4ZU;(2#y9JqQ`vN5$}*L zQ-VKZDe@G&{v?vLf;?;g!0Qzlo~zLb{Cz=HoY38!Avj2 z5m8(io?fle0cstcD1bukjav z=+i0^1wN!ANszb$Al)o$Y4^X|i8$Q$Cafh#MlOncI298JGVQa}n~`TdwBdi!^c$JO zCv&}&?0CNk@1O9z^V=I_ zg`ghw%Vx zZ=>^&6%Vqtg@y=$kiK#cwCS080R%9gV% z_tGkm_0bNB{C)PE6a8qL6wjNh!IsXpH+K%ypM~xgE&amfwDmG@Ql zz7?!0%jrnyqxnB8CJ;?n*4BtQoVR_*WbI;lY&q^4bs|M~NOx#Mj=6tJZPl2kzSwga z8+1{H(n6M2_Dt!!)pFP7mZq46{f~YEGd%so#Kmk2&Pt;$zW_Vq+a;^O`QN?lpD@vi zdbPZG$oPRUTj_-Bi?d@d1pkLcTzv@}r%AqAAe;k`$&K3)PTg&F?_R{W#7ET@( z4%Wgmi_NBTgliF)j?fpe*Zdf2ojBXA^ZUR@)v)B4%Gt`w<^um>-xhzxngk%dMLctB zdkNZ4hoc+!m23&$rF_JGYwZf$SwcY^!&rg1~ z9EzaAdQFe8PrAW!%7a0st>-I{w;t_-IHE(7!k;j77m}Z6lPz_7XNq0b8{5C6 z@vjL!eFis#E&!Ocd#DaC%qn}>yRXh8B(oVtcY|mJvLIhIRYAsSs~Hk+(KMd+{8Jxu zG^Y0Y>j|l?lap7&1(vuPaMa>o(Q`_6=GE9$sL=b;uP15YVQZ14hNRky!hO^68Xei@ zUuLkc9%)E7QlX-N`>?OPRzPAuo$SXEnX0%87f`KG4T01=R8t=X^O$ZQ+7x2sQc@vP?V&@dVNPv}P3LHMF&GH(L)+adP@*Jcyp zd8np!uh*abd=<1gz)gG)S4U9E;o5R^eiMJy8FQ9-&!p)?$Csu56HOug7kJ6mWo*MI z_8Uch%fv{+-KFoDHl!c@hzzb>!iTR0oHhE1l60K_2mU3$N+SqWh48<*a&gS^AfsV! z+Sk=J^Py(4d={W4UTQ}&H>)(5(VK9=GbWw#}sg%jmqr+57cMX$&1 zg`?RJp{cl%;9DK_Vh8}CTL3jsUUp{|xrF@x(W89RIJJA}flrYA` zoH0svcR3t+i)cD}LogZkOn>NKLg@$W3u!U8DCs6?if(pctncgY{&m5nth-Y!5reV?!1vVam4a z>Da7P-e|LU!AfJId%k-N(Y%Fhdn0cY)cIad@eZyFk?x}kWRf|@EeC|6`1y6_^SdJ9S=uk_ulZ_A zxt74Kv7foMbHJ}xl997VwbR%(1NN)6nd=a>WlNnk(JO&$+gdJTHx#R2shp;X;Ss8B zr=!^(ZO(akMM!iFVChdaBBN0qwF02Vtv31AvwuQDpFnr9ah7|D)g9mF4XkJo#%{1~3xPTnQ^ zAnchS22hhF8Zh*3C#EO?zz8fNIGH~j7aDZ?Ggj)|k8IzL45LFQocbn}8+ zQIm46ODg_(Rk`Q4M!lEtv|TUGAnR9B#P6~9WM@x~1>AecInLe$xqI1YqZTa%@v|*3 z=~oD2u>MBym`iM0qt8FpoHKa?dew{3fpdPnVNA4pJ~)s-Yo9W)4kx-dvF1FZ`z!W8~NXYEpCt_#hgNrF@y3djQWOnH- zPm}~2u_CMf?AA=|9zB8xR}rv|S1@+YdBcseKFPP@#CB$jj}R@9oByHi55O*@;*j@7 zVTtGJq@K=*ui?CI+%eVA}xlm4Voea|lf6EecEGqQ0l zm)U2Z#o{ufp=3X1fVdb&i=wpb&%&CW9+S>&BaZ#mnM{k(iuaWLCv1t@Ga_z~}bYGOvk>an$vQcx>|++Df@f zB{RE%qG|YOEte+qlNS7&+-Nl>s2b|+l-AOWDi|YK7xdswkR)6BoZBthEx*PW%|-Wxrnh-Yr!<(QP2o?+l}w(ST^H zOa4QQmam&jt7q5W7(zZik+a%bSFwMMsW$#S8{gF$>gMN0cOH*8W6M}I1s0&Qb*4`| zs~cC3d5#Dg-QXLx$@&?OcaibC3+Fty10QDpIkrZ>oKjYL&#@e3jq)@j26UY9t?XRK za!Bxf82pn4!Gl=?Kgo2yd_T?q?GIt+a=5C9MHxd->Cgi`C8?Fn zGN$_tl1g_RX_4c{^Il}Mfu0f_+4^wH4bpkUls?{5f82Sr^mo*~c5{}N-M}~IgPgQ& zI-JF8aGr}~uyr`DXYPOMUelwGDzF|r`)j_oDyS zsp$-Xc?RNs#BDdfpW6hvu)gmD9BO z3*X;+RiWOGIN!}dZk{2Yl;xv9RMRr{y4>O53D>2I@%n6JcoLp>S-aK0w$%A3_~(r$DgBDe&g?qDWH`* zQ-chK4hoH4zz@E+-#@=6)-$IH&05O52!|@LbB((s+g65fVOZ2gkqI$6L((~Egnvl81SqNHw$z7Tw$8Iz zJ^*(&ekdElo~PzBj?TTvhM*eoK`i71h#b3PDbQ0?9w?rCU8XB~oU(enChSk@w>(tI zSV`SasztmZn(6iCpM-%CaRnf}D|X$dVZ%O`LZ?N83z?vpJq_4;y| zoqi#o(ii_KeshjzPe5w$%Xnl<8S@j!iLCwkGbHYIpY|4V#hbY6^8Hf9UPzK;$MtFB zj_HZHGpS{hFDp>Rxkj9DaO{cTZ-PG|iaK9iULoFIM=@vqh!gxzp#;v9C8@MhrUhE^ z$QONe>2n2=SKn!yGQ+^%?P;u9L7RV{ziuw#HhrH~KSkXGNC=*F9uGXa2@a=n_DL?q z+TDl_L^#WokLuYaO8_Vv*06u+)M0vszh+z^;$YP9AwlzwkMrjSLQB6D#jvdlN+IMa zIDKi`f=fHz_OIHI*PhtdUW`$Z8Fo8=NVwwyh8km6HnS)CTy|z&d2}-Ty0Kt1KD;?K zilchS!DVWv^1ksq#==Kb{Ed*?_OYK!Ut-Y=#^ZDaq>j=wZzPBt61YLXb@+^fIJ94u zFF>V5!nO7r-I!yUFYC|_unmo#eo{1W`R_EEq1X~P8R`fOVXRw{1onIfVvl${QL1bO_iX}n%M>Fq_%3<6!BJ^ka`@V);#A5xt z-dIE5N?yt{hMzDpQ_#6ZyOB|o%qC*Z*fT}GH?p#N9p(juol?lUOz@$&4X@dPcM}>^JzLEUOVVtP|4>xG7EH4@V8W2%DW zw`oXSEA&}5F-6x{b+2()JNML|rv7@6%%j-3!3;e=Tn9miHkOmg?Z_;a*6u`NFbe`7hi@6K`BX>rgy6&fCTs^IrwL=r-I6XT%SMW)#r!)M9~MbGD$RXTXh7nlPSG zYRF+XdITbGb~Nso7&rN{s*^lQE(>p*HWwD2p>!`cTFeb+QO=WYbBlZaj2*zVlA{J0 zES+2NwvFCBA)EmE2LL^vmg$pj0X_Aqhur^hG)G}5lKnDLo^2a;VbfbW^Xlg}^cLap zyyqLjy+uumB!^p?q4^5d4@!s6-%m>87Whrkn4e&~D&f=>fu6q^k;K(TuLCS#hJnKn z`V|D)I<sns>#V6n4Uid!J#E66sIJ#AyH z(9*CT2CF8uLEH2h*Qpe{3HQmbT$6Z){~o@_px!p9>si)0O1&B48Zo&(5n_E4uQ@jl zoZyawxaWzLS*9nJ9dG$}9Si>VZ;Y)qT?S)fH!Ob^@nBu4lQqG-vACJXp?R#o(pWo z1~2##WryvOet%foH}NXcf^)Lz>{!y@B;j*+%LQx`_de?419Tm7xV8{ptkXw>kC_d% zLV$&;b>Z`#B&dA0RNK`HTv7g&X5StqB=iwyO2jQ#9|IAhAZNm>@hxEw<87f1zr?Q~ zP^Xc?`~rJr2?L*Tn__&OeFo`h|G8?6cWqAxJ2ox;K8!j) z@gjFz+dm7cdpvvtY#*G+J>Jq#%so2$IkTapsIlmku||{#uq2=TgAUJqY^5!>y+q4m*@Ebu_2jgLeE4p@*QD+B}y77sKB#QV#{3pJjeBU+R z6HU_a;JOE?vf=t*I4U<<;X7uM2f;2>eVgFm234xM%lZ*s)PoDx7*>zI%fr`tRt%g_KUCA7b)z6~EJlT74Nc7;%dasoo+Bn=YT&t(32+SA;aS{xO4%e34%y#BbzM!wTJ6iICVGnOC@ zW1IK|3ffgpu2V}{5U#x$=^(@aL$~c~Ej>uMo>PK|?mWn*yiBk=YwaFr zJTQj`YGl*T&?AJCTDlP*4ViFe6(J~YpT_kRD7mRXYzc+3$Q0K zWT%PmKeu*MyB_>bLa8g;`ngiUyYDufxMDB^(}kzV)@7E46WE#W7b+q1l0)CbIU16t zu&JJOh>Frw7Cuv8BkTo`&6dqw{U9H9e?9^jMV%y`oMtPpE?TpEdm+!2|AU~Ko8H7~ z%6jdL6n&BDv_-qO*D~)k^HR+h@X}^l3u*p2qmZG#5cV@G{L&9toCX&d2J)njx>8;K zr_JgP4%J?WROH0=^x}I4yxLlWpi|I-a6?|^<1m+2+MnQ)`hr2R7Cg$a<%pD3x1XcY zgG=m*bGyO#Sf;b!i1VNGy*{&Jd9>ez*iv^7>3rXGDD>>Gz+34#a08M}94*f-lf9u3 z;9J=}*JE-@q8>5JTlE`&nYncQgA5aJ~HnDANLC#${LK79Traipuf93QtlI$EAgG24U z-NF3aar=}+z)-SOM1**jeIHqNIhgdFb!?P7ni5yx8aDsm*~5sJP-K zC9vTla{rAlChz?>KD=r<7jOKHOP9pWglk9ISuJC{fv2fus5)nwx=lf;AhLvwxYpLV^^T(}77Ygc zT}XG)5Ag?4953^8X_e0a4AW4FwajaEXn8np`ChPw@p*d;lEl6t`~HlU(%rbUF@7Hj zP@6qWDRAX{_wzH|&KKH7M7hptyF;B$`)2Ol^J~=`cEN4u zDZr8f-uacZ8ZVAzyX`mPtHwa}0LJ%X4N3QUn3dTw;u7urEym@3qTUn#NWcw^CM?Oe-mw9q7+9a*y`qZAKkCC?WOZ*tw|04cBqG32_G*HZ$9b@h#spk_i zycpiKH`o@2+%}h3ANkn@HhFAO5k?^g@%j$7w!br2Y)%-<{LBUXw2eW;_u)yZXax_> z#TC%RE?xiVUg_KA3ym3CbJF%&+Y|HKr*fk~Rm28|>|xY?j_?fUS5_$Hv#GwF4bei_ zwvri4f9usnx)n<~qi8p|12~SH;QrW!QH2`+^ZjH`fg|&m^kk7)WlQtLOLU>WV`=m! zSy&Kh+48O2gua`&RpIfj+KwSUjh=Gzb4{RRvC2H)w;0_Q8P{3ZPvQqu9e)WLAJ)Zt zYF|_)j^?u7L`1xKXU24@p#dEGJ2Nf;1{~t;Q}w;7h`10x=C-9h3DJHQm%a=gA^av} zSzH=P6PdnK9JBsG$Xb$LE1UetwZACMZIVxC(4GHPFX?DnQU2}bYP3bOtmd*RMjXo> zkkQ#$Y0GtuYW$Mkc%M;)-Jh@@_Ue*r;y&mX;3cXDhv@(`RstzuEO|H!kyQu!nX4`P z`x@;WKIJh4%j}Ilp^*UAT?lF5lyM=bEU^bC}K2vJO-RDM;rXsUPdnNiR zi^?-rBW%YR4SJxNhMaP#IPh^l~7cYMv{<_*v-sPk`$Gsa!cfX$t9QF$o;yN z%Urj)51VZ+yVz`g`~3&!an9p>KJU-xb$JShGzW@?Lr1$~ec`lec|=e}fy*fuw4$&; zaEInqkY`Dr+Ve;R+c41H6Wc@d4=!-7s?eQd6tZ7QT}snldo=Agv~GwPY4s)lyWNLlW0)xnTCmC4Xw)h<)>I{Rxt^Mdzo8;ZSFcW3+3t{T_o2V9z1Lbsva z!sLl-Y^@?@YLt5#z|J;(+uyxQ@DS=i#_9Pcrs0)&Z$orwx!AUIy0DV~ zZPrC8@79oP51I>}Gw21H@!~-g3MHO0S%krE79)40h%b`Aio6{W3^#@0P5c2i`%Q~r z5Zyx>J~{vnj+YzMZ_k*pH?4!Ka{u}<_H)sGz$Oh@7b=NGl6IQ@2VI)aq2?VdL>HLY z0Nt(mWA_xdakFhGmWWBI%9J~;JPoHw*ITy9#yAg|H!1V}l5-1!pKgIhff9n9Ne6V z{ytc3IIW!f$Mk52*eFw(?Usw#u2F9d?f(I3i^koZhDC|ayfl$310DZ<@En8!=U2hd zg5s;#=2=vKanO?KJhH7?9Y0S0?KXbyMs2lCwa1fgn}38%9c;Nuq1b<09O$%LanQ$AbQeDTa{Tgy-O=aTBf(0#t0kfM6#%v$P{8Yc) zPdo5E{%T0#BIz>5(NA`WR_}TQ@zdMk)3S5f+FB3S>1z(Nmu809Bdj(RM_dxm?6 z`4hX@oguU6Q)~T-#pw~WM!ki3X|ao({^iVHs%8@*7BtC2$$lzElf_wkAAAY1Odk0} z$&yz!=D|)FaZQLZE5E`0MJ|--b8{84rg49Q5~>%f5MNpQ2WY(&K-GHenECGk1~F z71PPE7&4(KbSt*RwOlb$D@kq_9p8~{mce+ubQ2l7TEQkmxOwy=jjJYv%) zF|2x?D-L=Vmm*BxTJk$7SJG8tdmQHr%!}sN*)0@Y9q14DPc|y<7QvwW0Nh2?tyF5) z1)LqfPWDnBNhwRZl$6z>{DrtU#4kxHMU6+5u^!NFBCpSDf8<1{E?z7lx&2tejj#xc zvcoSyM~b2uuKCnt?lxoD3^`S@b=Dg~IQSi)ZR?ZQ!8|5`vCSY2{=xkJ&^5n)Lh^G~ z%{s_wPEPh-_6z^p=8<$HwX5uyE(|}RG+LmeIqX-ViP43-7q14cx_k+`2>z$HxKfs` z>%;9bE-IMFRHR)7p1v=8)krx80bXxpD&BnF;+kaUV16RxAL-g(rHy913T7|%zn2jorVO=1NnrRutt4ZZ7jG%jYiTlW-A+iN25%LM??A%jsf?!)^| zjnEGAe5@h%d|kEQuyCg|F0e+A6jqrTCWenqbnEgzBhwaP@)-4dz~nK)zT|Gj5rl$b zUB^MrpXB3^+j_zyC2~LZo3!Ei+R$HSw-fubR7}T7QWPaLOM~D!Z^peDtPoJ=ccyiv zcym~q>BE1HG`~Fz*l>%F^LLH(Bp57qNum4wGOpS=ZZu{)Vw zshjMr>1jO#tE;Xg+W0YF{kN|`?2})&&bqHnQ!_b3!B^Hk_QaK`!FPZUuWjUqWGva= zLGMjT_6OWW9Au(Lqxx}>AKV#n=AZs~!}PoTwqlP%h(*VRq@#}$(F=`|gnvgoycoWm zCR(o^igsTA@oOM^?@q}ZwS{#> zPUYgy#Ft?u*-m%++kTL%RasWJryFs7_gQhNg>L$sISu`miK@EP8A#M}&V2;4~Aa)&=JsNPNXR>FajRyTSMfi;@*g;^D0 z{|&BcBg-$f?qJLV3Xmh0PUDMX5WEz{ThIW-Slq|{s`_!)eDh}k-BC40Y2a^8%V$BB z^o@~gPGj6>(zEQB*v?*Tc11b+0btatL%cBJ2pVzerPyH#@w)?lI8treq!Giy;((LW z=P8kER_p`f_6FA!^l%d6Jv>}>jPVv{DJ&bF^UGNTxp`|0q!ZZn5({eESOuzdw)brz zBruRI+gj-$RON=m-d66_P%v62{c=rqw{~4my^&9v57AockXY*nUobzLL0c-$*&};N zZdeBArT^fKyIf#37|hYPJYIP#O;UW)b)EE*zSpNK{zT zH95a{qbNO;FNq{%rNN^4suW3VYMgjiRJ@w4>=-9r_>KOHwM#V1wB1J9D>Zv9aalL} zWkvCz(&}-$eQBc>ny~&@cFZt~{FQks&~EIc88u-_BcM)@EA>NMtCl_8grMaYhd1c3uqohXr<8P71 zL4@e-m`TzLglR#5BI@y>^EU0{Wq^+63BkRQe_ww`ZZ`By0xF}JD>%uFXF_nmzCz@Yquu} z(Z7ZDMs4OIqpdYH@ykIz4|9>u_SGjj`wyp2oE3RI-Mr8B6&1tN0>0u!NSRe{(2R2K zTjDr^Jp`jIt~yQmGO*i_O$I+1(IU;HG__W1a=e#oYX~=6!d^`oRAbv3NUEoAa&8Ha z!v@#Rj1-hjJgyN>$p_gXWj4#rRmiXATqE{Evq}yKj4Cy#Kj-S9M~m=Gni42kcUq&~ z<4ykX=4XtP3nB5Ns?+wuU%oU;{JYBSU$-sPgFn)<=RK^#WLL7)5(2(`Mb(Fuj1D&l zXb0Cg_O}EVWkoqek@YzEYi~%;!pZ|s>}JzjPP*v<1-`*27M{tCUb!9lmfeEw*fpH5 zAycdKYOn4kbxagn^=Aw*RyXqH|H4doC@Z8YNfo0Qi!_`-k$ux)$V5PZ5G;2F%rOfuEk-#D`d{#bj>Xp zm)q8m`zAO@$Jn?T7GDqhu~S=c4BQOTLob_IRJHZU^y@m5hx+X_ZmtQe6u8j-eC*01 zuPI+MVT%*pM6;0NrpL5T6X+_T1*?-wJzg+*0_qcPn0zpxI|pVo)+F;B5_Y84*?-TW zPlR|4p9U1Y-Ge@Dx+RFPN!OjRp{hE1F{2u|Q$!xr<{Z`-m^ye#P$*`Hs-XvA-V*ZT zJpA>@P>|sX>`4C*T9q5il-O(_sj;_e2lOB^x9&QUllDYUK*9{ zraK4IqZYk7-(Dbn{Ne;FVN*1B32dUwtcOXOHCuW!<+#3>T)o>~tT=*G&#d_Gvj9hd>mB%y4xeKX=S3pyR7wJ97 zvr_fHPPmdU>3W@e1*@i$zQ6P)YwN38)WQ3iD*rSvZbdMtye*=bS>gaclIk3dgl)U;N}qf~>6He=yTOL{ zf`{#RPh2bX;QEB~QYW3T=f{HXpLvV>=EN>L0e_+!awER1W#KXl;BHng6P$Cm|Fxzd za6mYeu>3JqnDxM|Kpp96tF_44RMi}GQ3*XlSZOgl2rH;Y$R15voo!6tEPaLq9^7wD zPhWWK&E@YSOhqgiWX1Gj$Y-UR+}yEj-gzu%YLYYfWKqmDv*-1DNXVXv;n>RFGtOsCBeCyo)A z@A@5uIfZ{BT@r2+f)?-SGUDdSYGi%Hr z_TSQS@j-9hQo@Xpw%|N@37gA?wd{spnR6keo6_X;$kmQ&_V}tT-gR7dqtUk~oiH8X z%){1^aY{&p)H~3t1876_m~M!aE_}SS_9YKOonxe~dQxlD@tv~Ns}xY=BbTCraj)84 z)FG@Vh@@z-R-DRoe7Xl0>b~x&K6rt3ikAvmDA}$(f1%N&jvMZ#gdHLoayDzeb7JGA zX`{mE3D+;&2x&MjS!8=R8nG*kUPz9Vs%r^zohprb-UgV@hdjc33i&J-v;+gvarzrg z#U5Elg(~kN>Azm9s;7)X&UVvJ+T1WL&S7X#vgi|HQ$RcLIb9lZX23p1?B=3~C@|^2 zlcCH0Ie$X9=21kyH%z5Q#k-svBD1?+zI)$hm6Dg7TK~8^xA*18r^iS)PG2H0)>s6} z%BdsFLM_0KHoI>%)PU6@y?2BSImyN^!>C4YCXl84mg?2W<7=0UTNLyy|J1$@s9yJQ zNa@xy&5B0-i?#ugVH@HNf2I~K63@6Hzf)w_ z%2bQix^bB>w!}!|uxlxL;3gN^O!~;fS5=Ng`L^+4eon|ROwjjMLpaKI2!7y=+1a#) zI5xMhW}{|%Tuzn-;H~6UE0e%I#vM;l!NTkb+@I`QNYZE^HjhP_Wjq+t+pooa*GTf3 zMO)SW;vkM<&NFXG2hZ2D;=Y?#Nsq0r94N#tE-}vDHC>thx)f)h!TJ&yZWp%lU#lXw zR)2o=b?Fi-c3Gnz;tf#$V6wv$Wue$B#b&S}cfr%pX!WO3&WCPU*KhE%RJvm5*JsZ6 z)q+2oEtr*@V&Vz~!c;?@-#CqIQDYo!64i&< z%r0Dgji*53m!HXlD{HGYYs8jzZj6|IV$IQZEtvge&1>hHq~n8FX4%yuR+x)emS@R{yKgye+1r4{%f>&X->G&{04&s7~S(cq!VeE21|QM%(W_#}tT4B+;y ze6~}qHRP&gke6<=LwuxNe%74*{5Qjza|Z3uP~$@GKJ?pA6~hv>L#^0`(yB@@e2n5- z9~YJ}Av!#FBhVDPD6(~qZh551XlZ$^e$XBiqCo$Vbej3Ko~)RcGXRWORsF84x(~1v z4|EITbr7tV4wxb#W|2z)j3e_oTUE|=^CDFHy%H9FVYX_I2fD=js?Hbov=Xp zOD?xNj)2i0V0IE|#84b6r6WK>HGeJ@XBEH+IMP256UmE0UwW~!b7kmDnz9Y477Yw<@U4teYCTPO zxZ3iHGQr*a#Ss4G5brMPvVF_j5%+KIbt|u#+9`*m3u=)qI0i-nuP3A*;U?w9KK&Z7 z;j#0HnX6WgrF=v2KGacasNr$J)j7KrVVQPN$`!E*%v=R-2i~u{cptGjV6x5R1%R$I z{}WlK`CFIGJyokUP1JUpwMCt0B?5jTW^M2HwOt6di6mtqfRnk8iW0amy>&Mew`9eVbwco%|+hgZ@ z*1UB2=Sti#$Lk5$xur2S5cjl@bGE5MN#HqBlFPHGJST%jSabF&RaL8EyqJ8YA+`80 zLvYw}@CbP@I2WyP{=e1yV=Vt6;v#{r@EG^eDL9_+)y&jH_EMkc627*N;u9Vcvg+u+ zkr=i=yvIV14Zgl5_2|7-?{$)0rAsTZ!*&xNq9-*bl&Kl>H-y$%b}+9wL`Sw7T2((I zTQ`BI-P~tFwEFWo+@{G-;rb%@=z))dt(oRpu&;mw9O#$Lu;Y+1hb?t2^qAD=+<74!iH2FK#}!pA@c&j9z%H5VV3oG33pp67`A7(S3h1I3b%W;|GG=w{0Gc<^K3Uj;wby) zI|dq<)mvX)49T$G7P1|kD{=n-zew&a;%s!CATx=NXcCf37ucdd1Ge^h<=tMQ*HkIT zeejsLY{7Wp9Oy|l{qhj(EVKnT-13yt2sm#`>DR1ob_&#;@dF(5z1N|6v4j0J(6^i@ zeoiqodjX76b&FpfzDNiUxUe*UG`VPc4S-6jpy+aNow*vUuxQX8Y|y0{L@CSR;Z2N3 znkQgpc>K1xLH9!Rj7EovwlI4M^xifDqUg!Aw;a@Beu951aVj!|ec>D+WXU&(aqa;^ zFO*;yA~zX0pes3vwPgdfsbG_myXa0ZjlH9a9NTDQHL>G4G0QK@NAHRlWBKyk2W+?_ zy|vG+8@YI;d!dpc{E-64tE5}XG)xPU>{o+VH_MEMnk!WnGs$(K*h2U3!6_N@&+0d# z6qh~;-JWejnb_C<;$7AxLg+Eb3%ae69i&{|Jw&$YrdgA-RKIuJe8uc>FJ-~ri@ol7w`=7&f$juY9N4GDq8Hrb`re2gN;7jqyu+seCyS}Eb=EV# zn~1N)Kbt#@3&v1dL!{01ua4EKu6ms25-)O5v<`5v?9r@TIfw{W=YG2=MO@S6U9C!WZ2xYfSjRAifCBii@Z%#pwmssLTv4e_6hdKtBeLzprd~ zC=%zcIv>4`K1?DSv4kh2n$Hqo3bh{do8Z^bUxo27Xtm>j@j@hx z##IRZL*FlX413A^B>C1F{qB%a2d)LA;NmdoyQ4A-nm*t>j5q5%#m_nj)q_*x?ZqA6 zBuRghT7~xoD|Z`EBMG9fYVlMWSiWfm?E?*QIoaSsuzR|Q@XE2y-l{Ki{;*(B{3#56 zBJ3sPny2oJqCYsD7mf=fZo#7fKDw;zR7G%nAO%)cp(p5ys&?st97K$nQ}=G=g3h|0 zDeg@^r6aQN%Qw?bBTn;wa57e4t+n3qZu`w{a(AKL86M}3osP&!z4#oh@|MdNlonFriTUf8P?uN#e#H4S-{l4DdC z!ZotN&2cfcY5op>0Fh@lOzWNuJ5#ZER2tq7?IyN@vEoBOr}5kes5DYZPcfPuR2VG#6XPPAREul;9eAi)cYZl+Q78<>AY5mA_)ya8+N24}Q z>aBPRt`6E))V3lqM}|(_G6v+~CsW1bh_%4Oiv!3!a76S~mR-qKJrE`d2P|a9Eq^~( z3DaEkd*GK|I3|#Y;_e6aEVX@b`@+rQ{Hby0It(1=K$khxs)KNIRjLlRn-6W2E#PeW zezG%jKYLvwZp|SQID8XYQ|7~MY7gceD}HtTGYq<@gdIp*jB|Pmp`F`3aW8ktZta)7y&Lex=#v*46<~Qp)t*)dGQ!w38p)HWS>(j`&15h60T<_)gVRQyGVvkDuq+HZ_uJa1xYO60Xxulg8tKNoPpAGgdpkUR1CGg%C-Y#n z+z}D+QWDASjbyiEuf2Qd=HL=^)RRLC`W{^V{d~kK3NUl#o~y3L`VwzzHvc)WclXkM z%$e5297xs8Wl0*N;l))a@B2M|$=|#69E39MU}OfmjBMExXV`2yi3xu#A7o?WMww%( zZ`J}|;nL%oz|qt*@qa1t2NC8ZA6mpJ?Fcm>my<1h@D{MX%6#jsL<8CF@zVQXjN0Rf4ak0DQ9?KQ_P>!s{c}I$0 zy7X`|(haHi#fhJcVj0d#?tv#K(Xx$_$L+x#UBzb8De&sQ?|MTi!V0C)+Eg;8YqVx@ zwfuF;7msmRSGRkcUlpz+yx4;l?KY}W9M3eo%Pd>E5Q7d6LwvI{LhKhMOk;4_bp=$G z_bGh#p`h8VoJW#}i%$&wFg;IM!;xY4668a9Zm2UtK!Tphsqh#Hq9HVeT^rChg%n>f z74xzp0+qLV1;vg1j_!5rnRh01YRN3-ud$K>ikFf+K(?w8r-aM?prbIuT-ajQhvppb zgUQgg@9|6Bx`&hpQ2DdPn@7%Fw=DV@5A~Y66-=;T9!~p={7JH;Q6RU#jx8&fl@{$_ z=~uu<(}wMsmsYeIAV=`x^)+^BVHydhpUoU(X?YvnDxq^}<92>keK@%^tzTE0&$%bE z&qa56S@oXdN)PioB!kbqc*njrkao}W-I%ezwNQx2*lx65oZ zKT5yTeIJrybSGu$tXzYbCp=kfiM~TS865m6YJ`nz5s1cnx-KQ zWw>AdFDRcA?95tbkHxM}`kwkJXt)WZ3HxZr%9-fdRhRB;@>Yd>J+<8&O*_tgppl#Y z1JNlNduLsU4(E;;E#r0-JAL$bha8#j6>d+_9VT`t~xh1{Q@)R`9+THar zab4|&4amGu6=72$@S9)qYcIz7Sm-1J@Eh`Ph|33l3Ku@b=i+6w&1R=TpPV|?mq63U z=YowLXuA+Dm$oxb7V zt^Q4|lclFu2;Yjt`=^@vTH zp#k%5U&gyKl5wMEZ{MZ{6(@ow3_Kav`gRa<8K=cRms9u1dcG?OLoPG*6@b2*)~?f% ze>WR0nJi`}53XG6S*p4uLMgg&-ii#3XCzw)?ZtYSeDXPV_`w1@8`I%YXruP?x27vc z$MoHNQNQW%yYB;Ws12jYrN+^PsJRUGD>)arN881E@c{<+qS9vkpBQ$N!QoSL`c~aK zSH>qaSm#+Rm|Hx$jy&{FLhUBS_IBVB`07H+1l{ReU#g0mDqC_1o^V2)#U}C1A@c9P zel&9oTwehyFV02d2SEcRO|%nsDqovndPdKcbm!>hh?>QN*3}=1tAelfqAoGdS!q-( zCF-i*7391Z_6+{cnbgdJTu(m_(VQE#G3W$7;b5CH9V;1hk&ep6+ zf(d_dcdyWLE{9oWqea-%lFMe9q=iO9cBgv%xz& zo}C>bTyDQFr8^9{_~7e`r!Eg#=c8?-S`MTEOwMu#U>hj9cD=Cg2BYb%D=O|e^@btE z_;1k!`%|0;yLtU*Q(V1NY}nn%nB`@*V)hkvN?s4PpqyN*j!4E?4IMzYf_nOgS&MW~leDSfm(!)sC5gqJ zUKO{3+DtbX&96FsM(qKhn7Y3nE){u0zAb2LGu4imj~2p!xtvR~!k(CI*nM;H+cdwY z`>$JlGU71+{h>Bbzr|;E&{4fpNwI>4gTb-*G5}1Mt%F=3`?wY0eb>&;))Tv{gGP%M zq)UMS$@kNfD6AkgJ24`^|>yRwv9E`v5=6-SM^{$@I#SFX;yzznY| z+iK8HA}HZoYR&7D+BZn3u8}E!yx3}A)#dXEqLi~S%qep1h$uOXeVQYmKtBk>Y|W$K zD#>XJpgUuFf9~uPT2b7rNAj#^x2Nl)oAMTMI;WT)u}ehz+f{#(w3{`Gs-AX)_jkteW}K98G3cCUh{6) zcaeCezUrEjx)$dIQLn>pRNgN>N2&`;lslhNQ>yj|KUc)xTj}qGCYve#*%tEI+vpx= zu&%|A_s?=F|M4b!myEtTn_|7w>b`dMQbRkE)!2T7w?xyx`u=yA=@Wr%bfh)=8!TVE zPIr4ddVLzj zRu|gs)uVmTf6*kYvhc~qk(~%auhr<3mHqZ7-pfOOvY5+o@xcee%C|rtdL85ljaK!| ze?kc`8FVkH+2_ZVmbCUv?4w@Rf5&+<qyC+E$^Paj@yg4 zp?UtIICvGBnry@#zHmo52CP?w3-~=3_=!IClMLBq+Dp{T;K3M&m;cp8`viLbO#Z?3 zq^nN$8`U?_8%`G-vcw{Y_&*K`x5#C?eE|VluvmX)Vc{ zrmqdzN|nt3HA%jE(|B3ClA3`o24IV5weQNFEiQIwKTk?@6HU(c)=%?J{o^>2$MM#% zkH^1;>=G%&aKCeqq|IpW(CxwQ=7;(xqq*VByB}(^I3#Y(T{%dEyW|D{Q)*@(~_vH}SUjGT7Aau>7v|@*n(NW<^6_ z7YuTYUvv$B#e56#kvTbIhO}eVdc#Vu@K~7Ir<}T=A)_H-DwCKidAvTXs!)>i%$jxX zbu_8}%L?gdt_LNCQ0P{O4|w_bgHY9D?M>$1rTJ^L;pMPxxSf?&Q}#c2LmL7VzqMHW z6<#5DPG@lJgJ1El`*^X-!)rZxhcxRL_r|@0zP|yxRm|JAQ~H_*(^^iU3N&#B-s|}BNsSeFF*-cs6 zU^5P(wRAUlS(=Xn)h*o~z`|*#=g(SBv)**}2j{RiNv4_=mvP>2WVhl^2+7(S&?0x<*LfM?rzc}};yM|^ActWbl6`RJMk5kv>( zF*YSPmI~>9RT-Syg~-Hme~FfpJUDWb+2}Gj{D^j9ZI88y|5mfFEfZJ8aiG1R$(|33 zX{@xy<6%L_e}uV2Fc+6uwPSR6@)E7V%^e2%8*tf-kFlAoN`dd;(h&u8jQ23aO70ih z2J(yk62B#l=sU>IcFx1~n~4=5+!GIn%!I6ITF^s zC8=yyH=0L&6P7_Y5of$l%WF}x%=Y!9u$Ah}FAT)(TsTZ?0>Jg-buDYV5q-+cuPn0D zjQ~viWEc7aB2EH#;JRoBSTVAvU5^n?pP$GO33g%h+(BzQh7lhK_vCa+n$PdjN}JIJ zWF_aLfR?k2=~-I}_hcp%^}BBy*XK%4Of)NCQF`0EPOSXS{GG2IK;PyU_9;Sla`xVV zkmKl77CGM~!N1@e^&@{sZ0A`O>jH}ZD>vDUD$RLg28CaQ;|G81tf!Ygt3Ra;}uJ%l4^D%XbbBDkA$yuxPufLQlGO_sUrEc;x^Tq{XA#)&Pp z;sec&EHsP{e@`p~7*G3Zr)A8|A6&*EFI1=_2iGZ3D(-DX*SmgkdzjRxlF-uDq{!YZ z`^2)F)VPylaW0Iudo8LHNVRplaJj~d_Lc?giTeFQhzmeeJX|j2BwvPRxE&YYU#SdwMDurfRIt<{#6SFQENOB2eq`8R z4zN4b#c1j(+6%3p8xLzqkndG{L%s@gX)k1fSrqicv|p7KK5) zC;@4Ya^fleB9A)K4Mm4ssr#mw+pw)_qn8%VxOtqk<%{MIa_Ssuh~Qo9QVu=}5syyE zng@E;t}w^a6*&^oAr!ENtsb)EPmtuUo|}&LdIvIE5BfQO4;f1+lh?!2%_jYDMg9K( zA8KgZ58yA8pjV9M_X)LPYu`~Y~a7!P>tT{4<0;ABA#+yWTAIbLS zc^s*Nb5g_y)x~x~-1sKBANWT=bE|NQT+&S9M|qd@H0?Iu$baEP(m#soqrAej8XXkg zM|^!#fu_e+YYT3PZZCMt2vp(KMr+m$_*--aF;=OhXe!j#GcMH)w2DIzpT@QZbhN6$ zn(SJ&SYQtRJ!3X&NL!>=049DhcXwI>KCDHF6vG|I4uJPzR!zEfN>%Av%2~l$tbZBjFWiIlM4IRsu(zfi_}SxwP#1qHxXe83SL~8agxA6y;R70r6Nok7 zLXm$){+&24qXi;H&!8qPX&vvX{@JKsG@mp}GHao=lY)kBt=Ml_-8`;5ZaMms>-KMD zOWS{QnTcs*j_v2Tm5SmduqL%(@xL~?R#a3L9PMA+p90YfyVCz>&U~_x>6DK?iN1;8 ziZ}7R%qgdNH%%WxHnC3q;^_#sLySM2XsHeW>Bx-dpesttz}aAry)N&gCMrJs3P?nK zfY-Y|?{BuD)y#q&*G_YGBE0c2+_6nx700UKb8m>*O1pr><;%nt-3f3#IpIF6)v#Fn z>yy7K=GFifIvlOU^B4e8<>ku+h6%h>7W@ajop2Y@Ql0KFqQlk7@O1vYlzsCVda6mR zR~JAFErs4;r2%^5$Wx#4-b(&sk=nXT)N#GE0qhmiK4Oy35#C`bYoPzx0kunjH+xMD z4pi9yn?f8r8Wy5DeaV9t`tM7MsY=s2fkv+ACYIA>%=)lr6IR1kroI7&@I3Tp;6&OV zw>e1@E{L%g?Bf-djd6U{V*yo+p@V@`_ zI4<8UD)n;G?JDMaTsGz$*Z#4YM{U zw?KE>!#|w~uy(nU^q%$b6f>mQ!EKrLulDC$4d(@dctuh^kcrg6vZmc`3O^x=CB~D| zELeIjXZQ=^s9U%yDm5Lt9Rt^o{rw9etGRCeG`EbSCdF4QyjI^#o4+o;MpHq3@K5c> zam`Z@O!DXfJlR{bx+g1!Y5bU!D+iGK86M#k8JU`u!SL0MpY>L}Nf@BOy=FJt86O#- z#LLHg5Dq)tjmOmL6TDc#4+g>82(3fM^7iuRsF)H})7GS6Iazq{q1=|6x+{dPzZNL# znF)Br56h|m<>7;lrn5+r2Xt-3<~4rA&xuhLH9*OS(x8dte--iH0*gZHC_Trfhs&1z zmVxb>RI9$Fz71}*q8mv7)e)6|1Iy)<;<9U;2x#6eBV#dIX7w; zpK720Q+e87lw8H!8g>ocNH20`yu@)MS2WUw_}*bo;k9>#fEJDFwX37nb0HfbVG+$n zQcFwD0=2_mRmp^j*~2586>ddZK-AnWOLs5c5{QYO{Yl{f&m^bAYmL4Ae;6b!E^{yM zj?l4;G!-Fe$GL4MJiNsluJ=>5*K!+W9A!aeW={m32y?~$j&Wy>r2q~X8yuT9eO-Cd znlw7Bz2TMb?J8m=@gwH%E8G(9T@=6>z#Fz2VZ0YuP50H%zb<$5Ix@6HE*Y{P!3y$! zX~}#qDhH>gACaZ6uTZ>7SQ) zdmped{gZx)btA2dpK17s1C~e#RiZ zLm9DeO0JEj=IGEpSvvlaf0soW1k@Rnp5(t;pE6kWWWD@`Z@cRfsV5)%*OaP}yC&i3 z8j*D8+)zz1zz-&-2@9Jx`>#7ra67m7`@Pq+_7;ww=yJ#SRWsy&)Z5U1C!2DDdRsFm zt`)>}>CV8eV2YL*$$~?*I|EShRK;@5iL^KujgsBzb&3l;$NMqkID``Sapxa|Z=zF0 z-U9sb#2rizS1sFR&FQ=a@g8F^y+)gVPxwiAn~nd$0ap5tWbl-D&2&P;_XLIbo}^L> z!7-`cZ}n$Ft;NIiByY{)qE{NgQT>{h&k3Quwl3>IiO`)e)7W|^Zk=nE`Tgx;2a~E) z1HI9~nm(z{Jad!WYj}K_Hb?(Mm_oMKd`SC@a(Xv54l}Bck^_pIVX9dE53a!a*|3!a zUj9PKFo^(h66*2o{Q9F}rSDg|gwb38cAaS)1P~=k19iQ4gZ)*#bZGh0il*Dfw8J1( zgz^uct*^Bv&Z$^W?uOykOHQZ(zl92*=-v0Px)hKghbnm<&jPM?&nGH+}8XntPrttv={QxV( zScU8Pg*2u3+zd7|ETIp=k`*^YUSst_pRPI{2949iMJ`w!6qOV0`~S;*L<(G>^YH?Y zMb+*!7+`E>W-Kpk#~%rW1UDT$Q8$d}Y+f?>dz^>AFMM~^O`!w@z^vuU-w<|f6AO8X zR>SjEL^{bPRKFY&or6AjrE6aqWdU?2gZfp0rXWeA>{5yRwxev z_eDJl}h*fbzAp! z-KE;C-G2N1x&QXqd!Ntyb$On1e1aa{d&nPl8C*86>(WriaFDLwj5>G>kICb{i_6xrbb!7d3~n zQQ56l)NX);yZ^$-v+Bty{Nkeq>p#(?Ll3 zo4>?HSIde!Q#UvG@AH0OIni~VqdE{vIKXFo4-gSe{s8oZq$!J~Kk{hTE!bbOIq*`_ z46xdS|JXI?c|d03xX!`{s?#2p@ULGQXS3`f_;oX~X5T-5F^J@{Wf%WEQ_yz<5@I3) zqO4dt_yq-m3tqRVJ;=P$TnV|^_|?w z$jvu>?aPiOCHL&aRo5+;)#cu3)588}Z^%<|%33{{8&0G@7Dq{MFNfD6?k-*H#@(4< z{!!lJM~Nq0@~NWdd68j(5ASYv$eKM7$a{BR_9zKw&d@F1sya3QA=`ge@;5d5CH*JG zdvkj^_mBD<6}1y=9$RC{=$vk^)%pVC~)xZ=V< ztE<>c*6#=o?4B7enr+Wq!7AbgF{$RCko-PO+}Ni77`!k3JywcWF~zlnzG-{HM%?PT zqe~I2E?TWiKev~ijSuOSR-c);Yems+AppB8?4~j1&}V7XR%9OhgEnt}lHYceE;z}U z(F%D3>?Hbs_~OYu@u2qV;ewavxIWs0gsLr1?R|(NDxJ)1&wCduY=+CYZ7OCwi-#?FOi*yJTo$=FY z(KzX)am{+o(wCRH=S`+j^}#A9P0)zSlqPnR@6=9G+k9O=VLk-xE-)^gT?zH$Yhqe}(2)==-a<~A+8&pSdD z`|G-BXW36j$r-X3k>a0@ibb-`*ZQ}n7zgFG7b#4A#gZeYPUyZu6nFB@73B!)K(+YQ0`2y{jg|4_B6KvP3+f_-cHp~k4bt)faSa>B#A}1 z##9l%*|BRNPfY|LQLW~c1n!n23&#Yyr^gXvneB|1-2&G-r>UnmTamPsMz_g)VZkHO z$souzRl;W{e&K>@EK@b^xi+v-R#6sPI(lhHt=Q;@4#fn!c0+$CvU|;V?o2%tm%1wY zQKk=qf6I>gj_%tLhny@+Qs(O~B$O6VLIRq=^TjZKk5J;EWrNy@*?rF(P= zis0R!NUrMkEFr-^QI$0tNEr@lfI0&AUEsDm=6Q~g{VMWTfH+PxaygfCo>g)Ksb3Op z|NX~JagYlr7xoH&H&*uW63%U4jp}@}Rr$%;Ogx8e3+hq4Tgb5+S0vNznAaiNuCmAp zl^n3ys7M6}1?H*HrCX%A-Ka9{fprOC<=RCW?q2WpL$|PE9S%82cV9 zB3l#=XSv{Et|3yVd z+uW$x{o_klMXvIZbd!wK08PPv{K@+@2WTqQ5s`Xyo3&)nA#!~8b?SXbzB6)j^?7)_ zh_%EuX8)x6_4xN4pIdltIRY8hzB(;5shP_Zg@W%ujK#IKGjzk^Cx*Wr*!5QLPtL|E zfZ7+WA2Kd0dHTvv^{V%v`z^I~EYbo*f6h2u1vskecnhuVRE0FEE8-Fb*m5_DsBq{L z7VyDIZXHrHF8G0WILEMrY19A~GV2(m-Dm^xTH->pi>)B>#^#o=Di*U-ZOCM?717i~u$eC&Gol1Mq?Qo`CA97t1AEw1iT6Et; zJ4bPEOl99q*f(i4Jqw&LWUaV-9lVWXKoJaF50s)%(VU6>mJ= zDuvHG&$R4Ms?_oIRq3%`C92k5vK1~%dR(M1P6IZo_n+4m{@AN6xRI&Px7q?rbEeN?`gH^Z%j_2CU94xhwv zln@DPoU~e6XLo(?LZq9GyxM^GTGk^8D+=2Hk<*;kP(NgbH<%VrfXg0munu z3=KALK=vjl1wU;SZG>;qew1_yHfu5{Esa7`Q$zm2rsI%Zphm*TV2XtBx}CT_0FuXl zi9I-?YnJAttsZL|2L>rB#`cQz{Xt2QAH{&q@@eXwmd*%D#MNJzs8SPK!!9fS?w7jQ zEMAkxX-(@~V`-M(<+J3Umom-K;-LZ7Er^xL?BEjd1U;qHhPeS!?2_j>q`iTx&Zb&j z19-bb+_-~Vxmp8O)tuodD_$i!8hKqYDWEEJ-Wr)?Dh$rR4idCl29Tqmj|v9#y%q1p z@`X70{^zzLaAmH4Pq8Nx*5-=Y#DBLQlHawlP zYT(5!dCb#bDBj&;;ylFc%mdKOJVhLuOJ&?UH#KGBuPs1tKg@GF2o6Z<3#*1LtgZ=J zL>!rYi}Ti5_5gsIb(=6mi5TR+*w!2CRZ?AA0q;_s+c;xv22!DncFS#KglNn}az0fM zHqP$A=qOCGPaPl(MZsEch7%6vYcFUc#3p1hEL&}BwAj2nBmVWuUBVsLBC|%dEgMRa zyaaOJvB{Opq(gUvuNn4VfMyq55@SS>h&Ealb?rN4mA~y=Zm*+ni zWpR8}sN;O`+%ZvSK;mdNsorwRorQE~!PcX+RDadEXVsBI;f4vr>vl-#z7H|C>-KUF zQXK1$X49d5c3_7dU-gfNohtV}41dNv1s|Fks!PL|vY!*v4R!l1yfs=rdCayP0-Ef! zUI3;F{2q&Yk#&mPWl%>W3b6x%*}TYI5nazo>e1{`ukEKXH^?4-+XJ3Ky%5+5Lff!6 zO0ha`H0Vcd9iiZ#j_kjL3}YWYyl(h!&Q?^Vm%romPKEtknn^738~#+4@@IFSEUG;1 z8?>*kZvTuSkrdc<3h8LUZVsez56w~NvyB8w3Hj?Q(ZT7<)}5pdy2R$GHn-O7dG6w? zQjfW6)=|~AX|HKDRsMt&Flluz3Hc9PX>?6c_{e$>?ANmQvU6EMKM_9#Isq{DIn_>; z$uU(;qiT1IrQo7cFCgic+xWs*w7M|`ZUm9iJRhpAY2%Bz`;^L39TY-rxr#Dx{RryOmiz+v8+}jy^_A>%!dFzJlgS$5V?V<(`<5pB8=(S`2h_6u7|>E1qqE zeT-2MWCgw_Ab% zCepqO7?MffsjZL;z#78K>U`d8k|rEn%d9YBWy^-SG#Q2bb}8YyBj2tbOuc^kVMc7K z-P0KCMT{EHS!kzzk>{j?ReR-5M?WF%o3{RGMvMEYKT%%L9qq`p!~}vbleiJN8zAHV z>o3^=0p1pWl{@?I00EInE_$7Dg?{N5|(zLqd@A2V{bHI z+DBd;Q~T)#u`I0BVGtHduY!7kxwn`HxY-n#;R@NXrVpW@)PbGMNQpm31+8SSzIFO)##m)f}6SyW5c$4S}Xxl7;G(7O@3mZILtq4^(T z4Ru#|qYV@%wOLCnSGb-C_{C;F6rH9AU3W)P9deA#JtBo1#SB4{Ut}l@4k$t2l6uvh z8tOml{hmg30SkW6Lx+7HR7ZeGOs>im2@T*I1Tc9XFA8qyjKy6?1^`c7%3iC1xjE1@ z9N7^_YQ1@TqZw!hqImF_HE^$pjuDVRn0Jje^nu@8%fc3a_Vk%{4TuL&eZMz6aO9&l z&UOZj!%I!3Ax)>1Kw1?>y4HLB3%`~&j#G#!?JaV0YFmgd%OuHj&PTLvTn|ILFJ+vZ z??kVXOI8bPnvu_vWcIzo&JiXPgUAiuxU^y;ZVzzYwVPCA!)}>y8Iw>#3Lnh}?qaV- zr`t+u2j$DcLn|9OIg3!)-fNL`t<` zGm_=cLMUz=-^pDfKv(f-Y4k!Ig|}vUllqgQ^B?%G!5lQ0vo31uv!^*Xy1%67?t$OG z{8j%2rxST(I-u)#Md5U_11Bctm{b=V7qC3H-%u3VMXCl%{RZtwYPE>)Q(o2SV1m0i zca2e^_ul#36ZN)lsa;7Iin4iEj>Vz$hfY=a?3}#lk_W)De;G_Xm;K4T>(B+Pc$OvAyZ6|#*&uKGgV!_^@J>S{r#-7qj zsbN|ocy9F?kbAa0YGSu}m}bjXml+1jW9jpAb;RYs%R~xe*sP9vv6i{lnxiD2pIFE$ zvzh?7vjC9I?SObO4fl;t=cX+jc<8~m%LZ@v)}s6RX46*1oQhy>;m`k&nDMb-Eb^TX z&$-8a!-Y9gDzrGMFefa~MDS2y&u5-;L+JTw5GM|B<=Z}oQ~2>5*Fa^Tn@A0W842 zq$Ze@#{R&Z=gx4e(+V4?t*iT+2<+?sX?@c@RtZ{9JaDEd#tS~{C1^9a_wjw zaK$nwX<=ZVl2_SXp|(pQxGkb3KVw?l!}=`Es0PGoWS&8=x7wSO!rSH#!~Wv_QmiYt z8EVPr-9`?@zY_K$MZlSvjXaboe`o)9AbT`~ah{Rj`hkX|ta9=v_nn;F%?gDww+Bqn zAM=-CKd1%jrt5wJ>#LS^qI>_gF1$e|xr|qh^|8l_eJ71EvFPK?HDMK_(x1(q6O3K5 z@H|4wnMPyGq)cbgslfA^N?k6XeIkFK)bMmBHktF zevx8ymetf{n2(eXR($G?vfmwG%SwVumpHI$>^g5-EZHUi9gjHEPYxQB353zA4`k%_uwJDHL$GVw?n=IEo*^$H%KlF z^wEmH9Q#}CTN#rdX6WgfDej*75pC|ZGU;Qie~;>HUx1m~u-S>l`;;@N0bZfJ7!&r# zyWvl??`=>Gr{BBHn&Y|~KC^6*aZ2RRO&IVEQKqt->J6l8>5bUCfoq{Az+OEG;okTR zT@O{DY|R1RW1pW9_b3)0^|r~Z++a4tF04un09@)|R@2;k;{h>YRu3dLmgKBvu|{j6 z&UR+cNl3+GlRc>gT;{sxoIiFA=c!bBA$JquNmsdIm~W8r_3SUN67XUA~${8jt+lnl*E?!cuPme%WdktlC!vH}gv=kwQ`&mR=9ru`NmJNk(O}E4HI(z&_M-|PT#C+~%>=mdOKjePev_g|0?DR){$g0Q=>lU2_UBuj0 zo^{t2ym)2#;tePn*l0JYfhkUc?^imtAuFxhJ$oiG>#QH9jdCe4^qe~jLq@i{8qg1# zM)9h@C%*EHRlGFJ^GBxv_7Y<_?za?*&0^E-YQ1Gohl`5=H(~Uz&@kT^2ffZu_+&B# z*)^NX*X7{s{^3MV@7D(MYbwGp^%rG2XMegCi;sO@s(%Ua<>Hg%$U~6wom?O!-jd%d z&W}M>Hh1gGgDZ`c7nz?haY%g-+H&_1?xo&B$d~lfHQ01&QXtWV51uM`a?|>;tHEjd z1Y;Iov5sZ1YxYLK*A2e0(omxOsz3V|@d{98U%UCjcip@m^9AL}cT(d@PODM2>@|KU zET}Z;fR#~A;b}dD%NMRmX>~8viL&g;h8JwJg`D}gQ{MqY^|I0s!wN<$QcG1G%z$x^ z@ZOd;Q}1X$03yqroV8q)sP(z5y<$PJ?seFgK`{wV`OXIj&8B<@sgGG^3s(l*tyfDD zW^{)lDn|2vmqrVZGZLlQ1s>8HSS~%tpe9#{DcaaCS11c=7CluZpe5ZwbS19W-sv&D z>3hi}c{iYC#R-6SO`XH=0e-s6M%5)9P&oUO@~)n$DNMQHeK~0~JM4kM#8H%Jo`^`54Fsox^hA>~tUrmc0MVy#6zEMYFrYVU0qC zvO-goCZ$w;X>PFSF`Rs2(uGOKtXVuI;(q8jK0V==fU0b)JI?jXF!~I-bYwyaHvyNY zxeNdUpf_B^;yUDA@D5vGx2+bVyWNvhax4f>vn8cE_)ITZ_+8o_su?h~D{*j;vjc$$Hm^em4z@pRTIZaB?ny3o^zp|XR-yMza0 zS`sX@zDDO;SZj*^T6u9BsvTJ2I~Fdzv*z{;{ugZawmM zqj~)-?)9y}ht0YSI0JLIu68Jj%_rCSa-)4u%i)mO(pw`q)+KDDY=38MFVcin ztFRe?27Ca&MGm2Pmwud``dnTx??86>QTNYh8@%R(Suff^(TvOGq=(vT&TJ%ox8!f zZ*{@MCoYXocPg&1YFv8M1AI~4#ig{dwi~krCz+?rk}U$VSKUwlAkuHY6#eJasj>cXR>ms!o!pL6@WH6&TFR4 zfFFp%RW@isbcG&Dk4Fh zakZT^ze_DdK!;|m7kM?%7najc1|tv_vUKYSYv=zsutL(OxjFAFqE|$CcJT9Cm?%}% zx*xXE81b$Pz*IO(rny&nrYOY698!c!tuVkb0NCruillmS8TY%(0={_{H)Hf|7zSU1 z8C*KOu``0Xd;C}b)hzFMmlBH1wxOxhWzZl-etrvfprdKfW#YQEox%Jz*<+&HBC+R7 zP0Vx5oS}hC(x)y@~X#$8FC(G1Q z<1#}LjRPC&woh}O`x;Ne+zYie=888JCrh^yR>Co6gclQO>(Ld!a^eQL`CHq``?${( zV}D1~oNPFf<~dkT{i`tR#9bQcqpYB7WZgn$P^fU7WN+SMSC4l<^4jIlU+GqJ3;u}C zu^7dl5A}A$w0L!85O3_QnHc$#wL4d;^33MU`3iRQ_HViDUC+th13S%$gPwEUuHJqP z)zj1u2+t2T6j3yKfYFbG4f&*?wQ~Jo468ZL!plqO+WviJ^7rW6k7oPWZq!NVMsPOm z4TM#JR(#8gh|{_lac#^p12g#_+o@*()1A+KQa;68lbh@)utg|->(He{o%?RNeWAP9 ziyk)#o;pE!1Iz1duL6I>_Ll78ryOJDB?t{S1G7^^n!@rN>RHBeWojq6lhUa$>xSB; zg)B6G>GJsq{2TA4B_#bF5HD`_lNIEp#-n=*kdEpUR2<^Y-0$f<_zl&doKD<={qU~Y zOqbZx7oml*RRGFLx%MA$m&b`37uVfn>9{80D$Os67xZ#p%ZTUVwZNMIR#@^{d0i+n z7%=V2-U^+o!xvFZC7v-bX>ho4dRb_ofwJ-B-RfHu=AUp=%(Jd? z67q)m1Kz!9!-dejuBj2oIch!JchGU8IAElb(rUn;8S(R5G8UpTdrsRZW4f;4H@s6} z%S9Kl*;TizN3Xr=p|u#Wo|=rB^D_+_fO!@u6I!Oc{x0l@$Yh3k4>YYSyD<&C88`O= zp1bx(F6O$D?$-M$FzVZAtk5g@^?93j)XgIEYvdMMH*R1J#nn;!k-fYNeZR}29xrb9iPf98-%axLB3C2(um>G4Kc#sp zao)77UkSWzbU1YhDfcM#nAs@l!VY&)@)2P}xpRMV!%z2vqF;32Ncey2gs@efe_di* zorc-}>M}rU0F6_1V?L&p#8B}O6msMTS6Z4bR)>6O{JR4EkNY=#IiY~UD5Ej%vPk~_ zFndoqI|UmqZc2}sPxS#{obB>953xJho0L~DvtnK$ zT=aQ%ycgcPMeY3l?2Wf*kqzl5yb1)Ye+n0tqu2Ahyx|bKuyE{>jPeBX&_pnZ+%I3R z0}B+mvym6^we5XamO0$uJ$UXOn)b1RJod4)WC0(*IwNl%XFk30bn~66SUFhhcI=e> zKdwF3LY{+FIS+au8U7mBR`XA_w{|W<13DOog>w|QA)8^gG4J=gx**E?Vzc0@B&_PO zWa4G;6zoMwjN~BiD=I?KPgMWuxluu!>0Uo!-o)v z!h}woG3Nu!tGD}V+{_4RH`kItdQP5$zCX40*zYhM_LvUkkA7a9okDbZEPuYLb>4L) zJ`J>JeI&pDHI2DIY|o!NT4Q&c{Z1C0MGXg_krSS^Kl`#na@qonkh`wB${rd{Iy%DN zJWS%&VR|+q(461}^RRx#mw-&FCQ(*CG+^J;Bg8*4#W%n6A3cEJe@-&|68IUe{YB}k zM?0a^CgT1y14=c}3)UM^`YrxONI<8$4B85io_r5CXaet3{^{JyyIYnHlayLWDcnS{ z5&U~QApxrLq#t3ch zO7$S6h+jnMQIN~)L7)5*{CAso_~);mj927Js|_ZtKN|3FjnqT5SqAu2W3SYtX=@*5 zrSvt)BjZBF6<+RQ|C=EeGp2k4#$}i6zW_ zF;;Z=FCZ^12yg8JmRnY2Ni<4Uw_*6G*?HZ&f+U3jkd_6gIk{0(#X20 zvI-YR9DS5t8gvN$WiyRGi7s|q#H6u13pnaMviHWY(idxqr*Z^`)XT%gz2F2nu8JX zNcO;PZoL~Xr@LIBbxWB=IFz;9h0Q5BpTdoJ5%8d=o3UN|O13XZJb3wvZf=A4C$q`+@nnXvvN>jO;bAYa@lI`*8oB3Sv7dN*C9n+{HJr zmuYo!dd@Eg`7;_@g)V|*)f+Q2j+495U%V!E_XU<8{JU3yRED0o4hESe6vSM zSqLhp{$qW1;_e+kWrmpA$ITb8aHJQ}4dk4Gdxs+CAGFf*(9(24$&B_Hd5>wysZrj+ zK8{AU!9Z~gYCQm*))b;Wx60Lm?BAhVCz28mXa6uDoKX#2(@;dpbxg6QE`-xQ*5ZKwm&8gP((g-C-`Q>a}?OD01@p zsYG)Nes2vXG#P2>0~BY)g)}EPpFs^!zqExe-++>@r_1B@WqXSKJ?#6T+5KpmkiAS zeiG=#SVhQ7(t1?i0inSP;^^u|hQ5yp*ZT(Yovbg!WA31}^Om{4n%}aLON=(UIPW^o zcJl5UdBNr6fo@{vJyv60x_f_D6ZQ1+h1!|RE}t!v$qB^@*yTi^HL-Zhc!@QBRhd&{ zjJRdLi)bTlfQG7$WQoUcot>FmI-SYs3#v{gju}A%PY2?dWnE0G zz+VDYSHi1+YNH0Rgf(hQ$jG_!O}G0DIJ1_t1g!Md5%keCVf!W6_tK!eD*V%qDZ*DrX!WeyWY41y00qzNPoW7X*%wZjSJG{K#UU_Dx zPUU9%Df*y0%9cuc$m~MDYn|zL9K5wOEg%4!x)I^d_*b+?J3G)tEt0M4cBzoxIG+xB zj}(f#D3@t^u-6ru0UG2`k6F|!!)dzaxa>dt=bwm&qqu=4%H`S$#7S;Dk}S3i#{hE2 zqvpBOu7$A&YwZk;kKd*)gF? z({RM5I8eRzWFZ&qqwk(&j&yIr$&J?$;nB5!=l<&2maiF1uu!#v(q~BQI(w2f&(k^w z^OF2>T};`D!PYy*`E8>5+LbN) zIJcns6OO61s%C#tGFO@^`x^nO^x&ns)WAEed41AVA6hyW#?ID$j`5IGC|_|w&D5_* ze-pkGwNcQDY;~PbJm1HQdlBxOEjC|jMk7SZK<21PG?nDcaT!<#HsYl+qv-sOP<4TI zez3!tIdJ~i6mbLo>&d@oy4-#RNL)=yKHfEP`IF-?aNEau3jG5Ra!WA#X9^2{ZNgA~mtbj6{QykjR1QUcX zb)8VXjL;!-S}})?RKMb-FZ#o7%G%XYJ&X3@Rw0WdTA_nzA_8#+!#&Iw|MSICA6hR2fXZ=ct&jqK^ zQ$!|7XZt~?p(~V$M1l{Cu!37uR%E;2-$YtsRf-=u{9g5}&nrrPVCw3Fxo_;vNVM4+ zj`h^y4ZfLx^y;n@eN8b0y`oU12)grXk`R7evbH$}4<%KaVNRN8A zHV_q{&2n_T;``T7hHnnEx*96D>HN7rx(gZ_x;q>&OBD}x_-&YT>i8jeT|SHDR9oqu z2SkkZqy88D2_`Rd<7oXhS!+sQ@HmN zx1f7>arcwkn_U`JlP67Od+Ke^=3vlfdNe z`i#SKcd5Mw&mBjfsn<-0sGjHPo_R#GlD}zpkUbLd4DiX;U@gDg-)?% zDCrl6f zYGU^Q=eZS{((+Asz{s#Wy0+SF0??s1#JD(E#=nU?4d*_1HBw(zqp;*BWsnIrGoQud z#7)Tgq)LLY`<8mUmxn}rP?^=?*bdpS(Dt-vxANxfpDE3KGpGT$>zml?jvdH|^(P#f z@mlAqudZI40HI$35sNS{1;%<3Ydhq~Gr(7MyAAhzMj0T5<`b)B=c@3h`Bu}$Yg(ce zEf^7<@&1mufd@}Sy~CPAih({hLp~P_Oil&KFHI{!hsl*5^Iyb9R=gp@>I5_yU5{?* z9zSGSVL&IqxQV=1G)zN}t7o&ptvR+Ep+2VqHbV3{1kigY!bmn}&UqvJ5O?<0QM zf_n1ejBKK9a-G3P(Nx|Vgyx~PCB{qkaX#!aJ2X%OD9+u4iej10(#^5CT-jz}AI*Rm zlR^DG@^LeS-#G-yf9!TV;xKTGQ?YNg;Jatkb|)f=C8#s<(A(u;fGxl+ocFi zwzBpM@?6q+tCaGAK90in3b@PgP3|&$c>7gos3OZw=Ga2Z4GPl^`jqoIt#8sa&Ld!q;oRAdY_g8@kRN!?z@uu3X6bV6&xvS zPdNDnh8jsXUj*sJt@`*&l zpFcAftki*;CR>ukWn*wcs$LEwn6t@o?+78O$Iu+=zD0;4h04z{#hAXmGw_i}SLAs5x) zmkw$zL|0@U?6F8n@6q%#2=gL=(C2_HodKoSuxgW=rN~hBA<7C@)0Ux{Q~S9q zexIX9oG0I77)Z`i9CDYx@HMJtq_{@qbZ(^vBhW*4l{eUjbhuJSBWIO!3G56sn0zqM z;iA{~`PWc=ZqVJqvf;0eBh0`P9>M;(R7 z87X(odjN`+sp@g>_=$&i53KGb+8GwHj>FK-9E8Too z1$zj$uZDa?JQ)EynH^)^khTW8gKBp(Gjof82D@E*rsVs1yJj9j_OV^p1Z2pb{p2zv z>4lPO-qnts<>ud=KZ?>&)#R_w6_@sq89MC~+r97Xg$+Y@r2Ju@lZTq?!#`<{omRfo zmOR0r-i)w+^Q#ora`w(5vPWBj(17BK`D@Ps{0AAwMTAt?at-f#%s)&@w-EiSaGJ~? zX%(|--3BcICc)&U9K#n6K(`PFFD2jo#$TXOHDYk@bfgg zQU0y>-~$_XUi6R~J3(5J;$PvqS?!kb!1){B@dxK)K^}3xHqT~kEA0uAM{?PSKFqt$+cO_?jkCQrAs8IE4#y!fW9MBr(F@WE zABAQA;HJcIF{Q_XJ;m5R^V_=D*Xr_!UY!TU+V@z;IFWDC#;K}_Wx28SP-o$4m-(Ga z@NY%9`AyyHj6rg~fd4v+bEeT|qQ_PI8`?PLqV3jmrh6~{GTt|@V&=>z#7^u@_JM#z zQ~X)MFXy8mA4g`Jm{V=>!V)-=yPL~csGtF$x<4ow|&Jo$2$@7(x`ynQZGsE{Cg~8MkL+Z6^$z(MMXwz$f4)#}g~; zMg`Yf3sH_Lt7$u^aKfAK+H2Uk?0QCn_74U)nV5O?Q8m6u_+wZ12F^Q9JsMA$C{VaRUA&rK+Lt?i~V zo*q8nUGsA-c0F0gXu&?s(DFaXv)YAsCfdr?3)ah|!*R`obvyAW&FLV;T=URsMSOM0 zjCMBld+qA%Lez5Aatlv(G-{rnMyWc??-ad)M5O)m?WL|qg@Z{tm(^ckv{_k?y1~L7 z6*O$XYPvq{&gxybBfNY(taM;IQzPr;ksOTtC2@9CcU3~w`_6psr4sxX`>biB(0EG~>(p-c!@*@s*5dXXF9tmHZGi@=rl7=xfE@|Q+y!Tz{)Ar`q$Z8?w! zV+dYdVm0Fxs?)wRR7NZxb1BzDwdqE88m4L8LtGrU{OQu5Bb1gyo)!Tgq1V~U#-^&O!u;t9LIB(#(D zd>`J&4(*-0 z^&UI8A_a5u_OQKU@A7TP3E;GssqDv0!lwC8Gxfwb!@a&t#wI_`;NuSElcj-|rnK$S zw2kWS2Kd3W! z%Xq8s%&O(QME~bl)xijZ^#l|C0WPNLe+`}cKa>3*$4L$)RwR9sEfJlVlE|=1CAp(g zsf6TK>AqQR*?}EGk`zg`RGV^4&gE=2=P7b#A{&Nb?AnQqv3+;_1J^H~$Mtw$ug~-S ze8D!Ubm35Cng4pJSkLo$;qX$$wPv5Ql!ezz?Ed`K z4E}da@)#|cUCx^)nX4<$ARjSL`sFn?Wrd-uc1OMOut?0Up;2O@_STyFHM1Wx8&<$8 zx=g}mX>4q6tILF)E>#rWzo7;V90U&D1aBLZdK9uP2YsER6Q^=1?$Z%@{dG$wydmV* zsJ)bn{x4jTEBCuW4+8_C|8drwiQ=age`q&FS3T*Fh>CZtj}4 z!5oFu2QtI3`hqN_F+$17hmE%@52R}C(&!L;JMzO`Ayg~9TTMvr)W49MDx|6l^w8_N zDwFz6(I0h9fDS?fPSOaah`Yn}Lf;VYwosL%Q4+bi=^dS^I=096;f`giAhOk7%9K%2 zr_9fcRo<@ob(I^zm1l#wp?%F(0!>Huha)Mnxdq%qo{ybFkoNRhuz~00&)FtP}?ei8JM;~2m|1q43>Kok`hU2FB z=P;FC0OJ}1=c4?RwO6a${w|zaO1bkhYQYk<@XIsUE-P;$u(BkZpZ@5nYMyj^ zsYo4jm*c+Vzb)PG-juCq`r|^>v>Vbj@P8W-D^9$~{JGUd(hTPo5k3KdZDj2$T{wSA z96bBKI@yu&i^n>j)c#uw=OAL1TjS3!_~3S;0a<7UV7G%sO+hYs3?W#>c5O1$jLab0 zi_IE~tWfO-Vh2f@Id!fA(1g>bBf4?$hP8*!FiI_l%u;6~<)s~e6zMV;f@aO08S06m zyOt#3Bz7^l&$#3?Pvu9UdiIcXUAcZYNyF)X98LGN{iYy3P7iof?)YD4kB2b4=NE?b zVv<6zm%P^$6mzD!xBI^j{2kAyT_9` zCq3W2!v|F-^9+w}t!?(p_Zu`0<)gS#$VD8KP5yCx8w7Ry-!E`Pf^`V}cwjIm<~%wj%m22QJO3>*%ox*E>2aPDD-r#+08^ZfAJUx*jNs@;%~* z?;$UXby$;2{}ZD=5;BB^mv7!JgvBC$6`NR|X9lgh%T#e~iAnm74=4hS-XL$Y zK@$OZvvwhL$HCai&A*T%+@g%6}*5H{qvNJJr?D{Xh>ak@jkt`=ZXjIA*Oe%tByY2yt_) zA=Jap$UoEdYdS+LxCP`4T?PW1OOcq7OWA2H+L(I0^%5Ga1y6*(L%*9Ey3ml~{12oB zXEL8RMS7Rp4Bb@w&@V=rdJ9Rc9*s|BHX-|*J^;OGw>yh$`48etPzOd;uQXixkz@Q) za`F{eL4;$@2KdC2hs;?v-MJz^ER)q=Ee5ta--iAG+GJK+<6JmG*BbDuMn{<17nF|E2miCfPO`zM0mKWUOK(!!^2M?~**Wmz$e`1?q2Gf#YO;L4CUf5q zZteNghpaFFieB1F6uCQ+FjKh(5_OJAOaG35%8|((pQFxEcb8laJCDTQG z85CHLUR(MJx$IcS_%PsfpHx^I0O0`IRW3t5O=K4tJ$WXI=YIQUqz)FwkY17{fNCw6qr2?AG-2QVhsicLTCC6cnE!G}&b;WCz=FCu&4jJh@3LDm6+6Fxw8@EJs5 zy2!OmWL2Z!T39VdB@k!9Z>r>msPLO4Cw6y5`&>2{U{f5Z7vU-oZEEh@JGJyWIJ= zEq(5he{)v6f}K78@;~PRTQ_WpupXBh)OQ}5-4S`8aOL*e!>=EDLZw5IU+z4wHCv3` zr8qPlKAY@SgoUk0{u{Mu{*E*u9053&(LC5AO~6Y2%5H3V|1W+&QI_u9vjyh$Dr$? za-h@1w-^z1l5ujUr3im*IJAV~{T`c8ha|OAH+8kH8JC}@+WG(1Pu-K;es$rWJBp^> zMDK`6mif#=KS7H@u1`g&ccsBiWuij{@;eD2&1U10zC$y&1j$WC%H%GRwYUx7PGKd7Gu5WL2!dR^Te@KVxFiG5i5<=WPU*p0BjJFrJ z!5ng79j-0R2NU~%ty0rilz{aLeX5wQ9}`dMi+jW9ERNSyQ9@-rP}%0Wol%cEhwAuS zQ5#o-LH$vOnKp~N`G>?L-J;dUNITXBbR1%OpfbV#vOM#?<77}p(Y+}XAxHLXaAlpB zhpol*-nv(n6s6SV&ubil`jb}fQSrWGvuC8mgdE1?ZV~SmQbDe99B4%z6}Y@+1)gyd zI}JJ+f|Ix^la|14azm!Xb~A?p0_mYtSWP;2v>nZ@nh;vtflzMa175UHtIr>lxZF zy>OAH7wZWn(Tq@rQmj=GQy9&f!jKKrwE5aAegpZ)e<^#&>wsx(fm#Z&8HCnt(!WI8 z)b-R?=F!U-lFI4_*ETO&6e-#K*)n**sH*+#+~DG|+_uyewF`#APdkm^=Fpi#bN6v- z8Px(32WukSv$S>PkW^ZB@G#&$ysqloH=k+2SM=o1)!dCqTpHz<#hk&52Zxq5&%l zCq|RoP?0|ub}a?1=c?8%;xp4&CBoAiaoKZm)n7fA=r|B5e#<=S66!VeW>FSU^>VCf zBU!LIHE2+7?sz=eZZ*JYNa3bC5;G1sox`%@pu(aA*I8Ir(GaR1^&EDWsT@im6V`w? z0NX~c?;H4zCJV#9nLaN z;kU-H8~F>u6PyFz46mslnVZ=|g_QHnnLSaHvy^pHBek{0ZM|2A&kaC_cAc2J0~+XNwPsi3iG zdtv(%sh1ZcqLARW7FC(9?KnkR(Y+YYrHq_l866?Krhb=>T%RMaBbWoR+x(0Or8SA@ zLGnhpgGa$yrG6rc`*0+E>7B^=5|G=HD)yL7Txoje_?Gmp{iu>HKUZX*7-%k^EIC*H2T2qMXk*u?m z*}Uh@;Kb@)?@;`1iL-fm@%-W0JYL8T6c~BqTDQ|wP*1F(x*~VLuNM&_`Kp-o75cfe z7wv_QL*f(kSRWf!b%j)IOuSJ7ytPIfXU#qS>B=Ljle9nX5&4>>XTXCXcMGCaZeT++ zGkVGuB|6h1r3*(D)j2a=9(76Dqhz`Nr@f9k-S`5Ran#}O=q$X3n2gGoWuXP&jRYh5 zXaT+1Gp4f9aJXFPQ#(JHtj&6{pLI;0&@h)!6Tpf8(UF{Xmw(0&dCE37>SwJjRj*|4{76-h`RIuBz|P)>A9Cd5s!pY#AzL8~ycInsOBwSSHITg+GYspR)(l?t z+JLv_pK^|tRB2~>%Y`opnuPFouZq>R*D>6FxB93Bnu(a5SRP9FL)^^Cs5(Rsoa9SE(uAKSk`Dv z7Q%@r9zBX+SIZ-}j5R$fZUAi%6jr$qJpQXKs z&8;$$<~ToTwEKvBmj~4E4BaXrMZ=#^To_&bOQ!p2I`n>IKr-Fd9ax->fSj2y;Z~`% zxr~nvcuhui)>4OE#CMG#4XD&Pk>!GFjRO_ZI_$a>DBAGt5`*Hx%io)h-!1i?8%1l> zujU`E3?MBN{|E7|(+!o^#IG*2*W0n{*@q3NOgC1KB26=cb!aL40*JQ zFXbH~@@C86w}S9LYc8kjEi%Xt6y*)=aE5uMUvi=Q_^3k}xKFn^#lZq>)mRiqYgpeK zFt#u=S<2G_Vu^OG(r1`Kek*ZUMty2*j)M8=jvzzc0cB<58mg%{0bubPf9#+ zOvXEq9pB3M47`&VZ_1#KM2l_I$#WjPdw}EMkF4#E-Sqfz$9ofq_tNq7zgN>!m2m{5 zJ~b4>)HVJliRwW`oe;`+TZPWQrH7?)QFnPMz)?-y>%8`SPy@7lfpljSL3s_bT+Tl} zS9F`pCyhR7bDdvk*44W1=_nV*YdBen;n!dvP&czfGk{zV z-nNcBku><3>$>xy>(q8-0EzMDR$gR9!80Xku|GiQksITxE4Sh}>OVjc#Hzq4`bEj< zltp`kX}cRyDb%%ne2?M%z?%OY@x&WK`047Af1MR(4qB?4vK3J~HzCFU% z*K;o#jVx!@UH^jWwSKTp>d>^6Y6&rOEoL$4H zW(z76eocy`iB#DJ{)<-M`bzr0WRWhXv&7{OY8RQCWEiNqxE*c3+4&*4&5O4IMqV$! z_XeWL{GqV9rXXc9WzW-qCksFz-{PlZCoS2=BZ4z96TfF|-~6Ekav1IUToV`m$S+9& Y!uT|ejf19IRg{n0X^&GyCvV04ADpaxi~s-t literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Gradient-09@3x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Center_Top_bg.imageset/Gradient-09@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..643cc837be2886405352a7bad9f613b97fff4744 GIT binary patch literal 199974 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 Hu0mjf<7!Qd literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Contents.json b/MusicPlayer/Assets.xcassets/Positive/Center/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/MusicPlayer/Assets.xcassets/Positive/Center/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Contents.json b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Contents.json new file mode 100644 index 0000000..896dc6e --- /dev/null +++ b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_1597880746@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_1597880746@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Group_1597880746@2x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Group_1597880746@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b0fb1ed44252ee6b91374b93fdc3d6ad1c8f57db GIT binary patch literal 1245 zcmeAS@N?(olHy`uVBq!ia0vp^^ME*xgBeIZ-5mQDNI3=ggt)pF`242~e9fKE4>V1* zB*-tA;m{ie{TcUuHnS($3a=^fI2XDtppv;SVFLpLi>arJV@L(#+o_%FHfsqSk7P+H zT>JUo`YLu8*VSv)EuSZS`shC^@BNnyo1V^}R{kMn-G{RB z7Yw_6DjvzrJJE9e{^7|pKU}f3nacTIPWHg&{QlHKyJuF{s_khHU$?h?`s(+0=Sa92E5kId?nEh1vzVS=3eakNi zUQ(R@swlYTLGqh}C9Wu7|Jtyx@7(1jyy{PzFTYZA^AJOu{LM9tb=>((6U56El%_D0 z7FGLk{QF)Z#PH_jY&Mra>F1M9uiy9E*M@slyy3bJeqZ-L;FMrb_|^Y$%R(NB+~)bS zx^&++bH;fu{q@V%ZC2yK`y1X~4?VmdzI;j=fE7KSH>k9N0PWK;Kp~Ct-=ZUFxB>dvq>d3zAs7Ra$PIsQ!tq zr=*{jF=VWLdQf(2)NQ#L)JV>=3VKic?8_q)=( zp7U+3F|oz#dw0H8F^ZC8bCaK)-?GV}Ue1$4PzgzxE$dARXOY*+Yx({r|R z&rV+Y!OPH;X{mof}F!X{?b%nuuPJAB>uu*3cP&fSj|o{y?OuzB{yjQxDqPo}SD zn$cn9vpkD$-~NA9kt_5**06dc{t)ChmcL>VS=;!X$zxw7tGV4S;g*woL}Dgb&Y8q! zIN_JJ+`%NyyAMlWR7bU3NaJ8< zhlZb~`Ozvry>k7HJA42BZn!?@;ZNVgF9Q}A8|{5~bwSe8m8>N!&dK+Zqq}Rc*-FL=X&iA9v($HBB*=MAr&R#gm|0uNk!xg^Z-%C5?p4{4DJt2GY;ZylFyA89h zPdghHXczfX(7&Ndwc=i9{mGX(68`R$cb@kzTW)&%mCEwzE0-_vIwlu)^Vp;9r{m6E zHtKZUZ@Z60u1IV! zReZRv`csM1kB?#UA2`^Ug=~_JKUlJIQr)Ay*$10`#$LAa2;X+;`ufTl_w1)WuAcnv p!4b>77Te!Ff7T5w1m3;0U+CLob$_Pweqag1;OXk;vd$@?2>^ukhp+$u literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Group_1597880746@3x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Artists_logo.imageset/Group_1597880746@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d5067fcc6acbe8a92e71d27ef0e3822abf68d4 GIT binary patch literal 2213 zcmbuBc{~%0AIDczj!-$Wgj9MwazxK0OCCvx$i23lIcCPF8JguNSFD=MQLZ-0wYf$$ z4IPeAj)~@4<(OlWE1QSdqu2BQ@1NiM_46*Nn2(9<&JdAHHJyc2bI-f>Pf!Ut3t?|w7pc`z2|+24_? zsJYAb>iyZ(lRrKUg^He*4lJ3|#XN^o@n^CM=GU5AOtw=il3jYJ-A#MsJgnnQT{&pvi08m|`}v zai_jLXrzv@kZo!o{Cmn>lwkdKwH>euQi<5UC)umro%(^~S-Gh4JI50e_xiTOfut`=I6bPLfb;t3<=l3cgXUIs>dYxptGY45iSl1& z;?l1^86j8j)jeU&3Yf;6=Gt^Cf*bXk{|2KvrLiQ0IrLzw`Bj80EcOh)JZCC95aaPt z1mMq?wDGs3b5+B{zI7IL3zlH7(t`z6ElP_2lTA~{E}gR@AF7y3T4I~=cx#DgqASyi z;%IRGhBA3Ab@C89W%C5(Zrt!2m?hB(ZOQXuy}P-(Cx{pq$6~@j2LTNK@MtoeZ?_08OofNay^YW++jP?mx8#@hy^ z6}iU9_Zam04Hy4pYgZ`>or?1Qs^<3T3+#KqM-1=eVqTWv+wb>nsJOzW8YmBGT;ax> zz36IPu?yQ5FQFrOMhf@Z!~at7B^%0j%GG_4V7~uh(yYc?qIK+EGemw&q5)=C*WwO6 zR7NB(TpH5Pc7O*ZlaHqUu~Q6OJw?;D{#*HuQ{@|*t!NSL8=$sh5O>);D48b4O7gD? zcXK_r%#0B2W9rDUWE{=)z~ZX~m^flnXmGq&gJ11H=_pv;d29mVk=$UeKMtfWFK$L5 z&Vg@@HY7qbFGR5m=p{=z+})qQ_Kk}_K7VS^x8qr@He|QY>ts=ThB+kc{RNpGO{}eL z62pX)c~n+x@B4fcxS_O^vcmBQD7>CX%0#cGh2MAUJ0GtPqm!nb8j|9^)f_Drd#bnj zF{tn0%e73mmcJfdh3p2eNo}d`j0Et&mM9k=R&lfiSfQT}v`9lBh@pCZ5c#&c5Awxn z5kn#G#(nULA4|-*4y@=<;bfvxPq1Opp+7-mGt3$9b)QA$uf0!lQG9p9)}owlCQ(IS zf1aQ``>tF)|rPHl_*;-?EEM}5cvwMt)V zL)z$DoyyB~UrXzhb>;DXFYuoqOd*fhQm_)q_ zEy~XbEJ7n1;V9MP2j2y$o2;f|4zr!MW3jMzy)3`DC)-s@))(F!mi=O-o4Z*o1LcPq z*d<7;bc|Wp;-~*ilD(Dmh1UBk(Jt-YF>FwsE4xr7#(&y2rJHuJB36YsYI?X+!BSzj6mg5q3DTaz(W({8kYKb|o3Lw^(oQ&ySiYknbJyPFJX%xq@Jd+r$1Lrjjo7P3k-LHrixC5P@JT@1*<%i&0# zVgPQ6yI)FEN2?((cX2k(BX`2Fw({|K2x?1OxAk7heP;)_&oKfS$>LOAH?<5rt&+HW zDyQdc6Kw9+Yw!I%FHTXW=b|p~Jo{x=iAkbc!#*96RY8(1i?6Xa0yAupGYdOHs|voJ zEWpbfm(!^g$#ydXPe$0%?2}M|bp=EML)NRw*rZ|i=zirtQ`JK})^V3Ks*`Qa?sTA)qV}`XBPHdWLr}frR|uf!Qa>dBP>6Z2Qdx#>%#X&U z_HPVe-AJmd(WgyFif_}H#JLAXL#)ORRs^iN5uOfAJ{?~|z-$+p_xka@e z)<5nPbv=1-0=+)~^zpS)e_PnP1`Td&#EWjU#Pa6&1Xwh%A=I5q56CSUHnkh^VRMAQ zac4qEUwODiv=#PPynfS@%qx3aPOmU}M#YzR%wHM$DHAwg;ttVXqC%LbhQcBP)C|F# zmxvqP%q_`z{HYpCtg}ac6s|D5SFL@)ui({W(o2=lg3>ukow|P2?djVqvmAA6VBJ|) x_@0LKc6Xu%VyX?JPWE*4l5uzW4>W0OPjc>Qz-{!?1KZC6z{b-4BK3lI!oPa&hpzwt literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Contents.json b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Contents.json new file mode 100644 index 0000000..24e0459 --- /dev/null +++ b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_1597880536@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_1597880536@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Group_1597880536@2x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Group_1597880536@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b92d8e658e0cea231f59c5d2fdd64d7300c19323 GIT binary patch literal 1421 zcmbu9`9Bj30LJIoL^kEh98J#hI?DOFR_4BD?{el??s=JVzOzZpu@WyLYA74Rl5#}6 zpGmGP(KKf?L++zDjaTgD^*_AN=kq+@-=62U=Pn9q4;GUb0{{SEM+Y19F)7CmI3aYL z8{YqtkAXnBxZB#_bUFHe+QH@L9kZ>- ztHO(pHdgLFC|*(+QMp$V47Phx|2ov1^@3vpB)Z z(8J{XlyP;sQa~Z}DB6f_1htq9m#Ps}t&OJ@uq9^s0^*D`O0g6)bv@U;PY+8sLCdsP zG;$nl(S9thJ!l|aJstK4Ch<9ax55%Dam~wcT7kYo$P$|>tvm0>LfojL?~qz|?dVO* zIZgLJMy%PiM7;emHq~l6yM&1HAHedo^mNEVkC=KN8etuKODdU9y^IUl5k74fR2=;% z(!R!?>rvWyxA@hBd@8PPpN!s)HGGUW7=OaFABp5`he)-X7e#H0@5fukfq2PD`b)e- zr|?nt;WRF!&A+vNu|vW)4bY^!DC@u8KK4L7j=rmEDc*C6my{Jhnh3AvUKKbzpsk5% zxW8cksIpZmXJ_r&eKs1Rmw`wvbqD4Ym9it#`l>s|yevHHa1a};>=Q#;iM7;bG@=H{ zY_3SyR-!YAOGpode`98Bg0rrD)2F}Vc;}t9B=A;7D(&;m-m*=kvP~9-IJeay9`dNb zLX7nSVD^N8*!gWt+?F{OnHsPf@+CFuP3DpnD>)R_3k$;Hhf_k-ELl1E12-87>`x0bb}v-6uspWPWr=NS1nu;6h}>VgC}+xuIfEgwScL3RE}t z7dI`=Y3NcBxZ)TB9-VX;ZkG2wymv3o?zeX3PZ|rK=s@nzET-??B*>WPfe5p+FuQ|hI~{CY zwUfkHlp2L@;%2@Y<6Sjg)f%%kCyJGHpmITW){u;bTvr}M$1@e)Aq#))ITbp+Xt;d} z_hu8-0W%ou0tbh!&kt@Cze5w9tg&i+T?U^yH@5OqMcvF&rl|)g6fjt_yFq>Ubtxgg zZnNYCd)BPnkFyCKYLSJP;A)lP5eo_4>_lzI%glN8uxHvA@3XE;#ty+!&R~4jlvdN2 z3WmuaYqFm>PeD4RF$NaMF0GlYF>j>gM-x!}#_z)8VXU&6&Y9l8uk%nrt>_CWCC-L_ zdS}#FL+!&2TfhTm!}!!OD@}Cew&zv;^JP4AnACSUA=`mLloS$F-J67koqC*(zuYSl z^gKFH(UZoha4bBdP0F2Dcg#Z8m literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Group_1597880536@3x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Love_Song_logo.imageset/Group_1597880536@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6a759c0e7ba6ff629c8d1a38cafc89393f8eeb3 GIT binary patch literal 2542 zcmbtWS2!CA168z$)nSzCqUtJ9tB6=Ns!>A4s2QU~D@GF|LAljjd$iP!O_FSMQ}O{Os3nP(*01|_k9>t@s!crQ>hggiX>5C-Z$ zA)VPdxaQycwNHmA4Qxx(c`W^X3cqxdZTmh%ju>USQSWQ}J(z(g&YBaE_&_}c#FSqR zX30lMY$n}powPb~V_AM;Q}B`Vcn98O&lI@IE!`}cZ7qa?&W>c8+(gG9()_b6y~C)# zCYEhKpX67D;b|LA3nxrH;Tdk53yn(m`yzLd{`@N>>?>P5Vv6|RVnJn!lBTp)Mf(CJ zGwCDxeCWzGtI5L|ZX5rs{*pli5$gH7#24bd%I*X2@ibb3EXzM@CTVct2kKKR z&5`Fz+8bq4-$L#TRX?ybXgHBKmTR}r8*7Gm`cr%ECj&&X)l00vzDZIF>z-XWY%VIJ z>1Lu)vpy^{Abwu=kyAWH%ps7cXQJ=Sz>BJz!v215>V~P~X)o@96yn;MIj~)p20N>)DL6vqStoHJ2f7abSN^lxQU*x5NXUW;NPK+n@=wAhUfbm&551n_(lfm}^ze zXaItEs$NYHl8oHnjx6v{FMF9AB*Xn|#YBf|cES6l<>8BN^wYaWIjXlxBvNd5ZW6%e z+mp+p!uxR~zm&v6cXb{)t{I$+0igu)#^EXrCUtHXRwZB>$>n z+k?OB(P@A3gWWukf!f9AmaiJ1zGk`VEy)gx0aJsD^iucqnfyGk9u2;`!I8eT`AP+s=819f9S= z)GC7}jhetN(BXeVk6QnVqCOl}fJmN4#+IGiiv^p|;g!#%Y#O|%L-|jV{wHiEQd2`J z>9bEXS$oqq7QCIvxVYB{O1DK@3E96?H~}B$h4C<+?SSCAlZO}Atsc&{zhJbxXZ5t* zMMB=%bOW(vwM#X=8PUJZywS@uFr~ev7}$j0rut4V*5J-0#xc~-)-g^d|qcFkpjo(C@am2 z@(JX3%%6S@NX}MUb_=uIaRKr<<|PTTL=zG~3qlHNY_|^$?&TeAgRB+Htz1{$IuSWx z^8B+NqD{3z>}73`xrV3(8L>|an+&GCt$BW^*D)|oRfYb`&`7e98#Cc&3UA^mwk zg{N0xCZBo%fM1+>y66|?PKh4S{;09h+9Lz)<6_O}cvUnI^vFm!>i~7~H~017?jKXe zIO+RQsz$77?2B2bff`BHr7pJ^3j^B)q(faNv#pbe+wi7iub5}>bq?7E z2yIUKlIx4le-T~nm_k?UQx1BWYcnG7@m9U$aoyc|XUPoovE335JR;~=h-Wucs@O#W zE|g6Ncg)`+5)5lDL={xxW%zzTN0lq8qpzPhV;oqP&At}#KEWSa2lRfrwu4r?3w7+y zrufaO%;Wm{uvX<`MKi{AhenygUnoWUg71H=ZFySmi$`E$WUY{ys|x;(TPnr$0Yvypvb?or^nSSLS3c~vtH6plb^+rua`czrmB6*j&O9}pZuU2Te*hO>dP5k z>Ix^D@-T3RSG9)wjPY?RZSJ!KWcq6XeH|s2mpaRH^9;`73tD_gkQpu|aAHN2Y9%B{ zkAH)=p^e%lB`I=){ZeQ(Pt)mnu_}o+;Z#cS#k!3CrH`p57%aHouSI~YGjvsY`rb8| zJqf&k-%xtJfO{`6rh1A{#dd*stem>Ts}w&r7?6VTlU#)3pflXAStJ>V8uITuf-9lb zw&z|}?{au*?H0SgM>o5_(sgYV3JAfr+06h5yx4YuDdRG4;zaF>Vxd>74)vq~!}DTZ zJh`^oBMynD?p&r=^c-Msn}}-V=r8AR`OQE-9Tciu6PjFV%&h&9h*qQgNWOTSxeurW z62TO%!D0YmXN>8+8qN*4o3I;LR&J80i}$Q`-`#CgSInv8=#WAb=Lh;5=g0zN!zUh_ zA6k<`snzs~unl2p`S5$e5jDeVi|ZrK0UG`ruP8?-APrp^4Wtk|oh~L{Q6-ZGHPj}K zedJ<(#JbMs_PVrT33-Dafe}^_DvRH0K27G|0hO+i^jS<{9!gY4XaK@;{~)@ya4mC>XEuRdE%>pfm*sYI1h_#Kn9ZjPH>r9qDPnhHr{b`WDdcf{04O6sA{^wUNg$w3HH0i!bd|Z9*_o#ajnB+6nk!~TxN9B z{CwCCxSjl$%B4c(3~I<8bBu%+3YE;CX_M>q9V@p>X(^l5*s*=%^VKEf;=3W&T%8JUD73;BiaRvc-w-#=(?#0s4V3p5&xLv*~13sh^)z&i8z_ypaw(e zi~M-S%D~(szqFf@tF)J)wNs?R;?;Cyd-dR}UPh&jE;~;%>Jm1sS9K1@4(2W@w7(1e zDbLd>C@b*Oz0MalP}7uuT6XMjSe_E__7A-tL2Y8 zYjwJd9BrkCncU`)V@NtVSQ9+C)#@N2n53bcpguL6$x0s>kd^crnv<&8n@Bv>P9A`C z+6(@<0^FdLEGJghPon?gC Tm^$MA{4EBE4ph5T(=Pa5dm=N$ literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Contents.json b/MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Contents.json new file mode 100644 index 0000000..487ba23 --- /dev/null +++ b/MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group_1597880534@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group_1597880534@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Group_1597880534@2x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Group_1597880534@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b190ee341375c51fea693ae02e75e91e32e04ccd GIT binary patch literal 1678 zcmbtVZ9Ef*171h7(rENYUgk=aE@S=IMrc_~Gh>*DkxBDXLbTY(OWukRGP7ov>n<-R z6iY4h7h{^YU8b{>be3#ha%7ZF&d2-ye}2E`<@bDezCJgJUhd$X#ybH302p`L)pyHV zw-^f2*izxZ(EBYz6Fo_8?(v?R{|7vn9HXtNJxRV^et=YGskz?cPsJ}_KPriDK&L}u zG?#W8NCANDvp825Qu4Ni@)_3Lq21YpqVKl9-8|cN^VaGgiIKX3$PhI3(!OpjQUJPR z9nUX3?EuNLG*!K|TijvtE@b+^9ATyD)nQZ1%SFPzcuPk$kg=~GR_W5aMrBCmdGNf> zIFF56Um9h+_Hba{3b4IYzE95r-1giZ)qrg$XuEzoWrM1WWmxd|@Vqx;70auEYOGUq zAme4TNg&beq;RkI)bPV#6P;YM!jUvg1jQB;{&EO{|ATX1NV)rfszZYg9FtVT6zMgL z?lb)MvVi+Pk6Dce7Yd_`<(W-b{Uc+ToM4|eBt4P%F5>n8Au2WyDhz2wgQJcF2nHa1yR>3mzcNzy@i5ht!r9`W?(p);XZqy#zz z5-ZI_P0~rW8FMmKPs>H+&-_Hq;QiSp>dKSe{jK-eAv<_;<imA?(ZKHn#uM{aypp)ZxYuRnGH*38q ze#Zc(b}QrTl1f;vn$=S0aY?|rH1+7e#MYHh{i;=(c;^IMRGz)rV*0LWW?Pz*;|S*) z5{^V>m0Er3;XT#j$xmT(?)su-M`^q?d4sBV#Fu#7qfQ04-tJ$! zC#ag~aZvo9RZt^u;A($=gKINzaVEfJw0MtUB-|Al7v5uT{L{(WYBOk?U88wokp1f` zTuxg;X(=U9mWc7jB;awWIaXhO1eHn7ANKQmb)An^m6g57Icv2*0bvRzy!-3JS(cqK zHWk0FQm&~A^1n%qxb_s!KjS-Zf@(}1F#4GYGyk&^{nL8~r`DqJ_AX-!y5^t-Zo0KL zBBAU-iw&<3ooQ%gNXi8k5awM7RY7gXo!K z9!<6$_Cj|a&W{~2?K;}e3;M7eX5lpCek2$OJCwg`@QIYl1oCQb<>1~2t{GR-Mnlj1 zZA7mX3aEoPD9fO^ao`w{dv)93@3#>b>K%IW-l^Ls)+IsO=;NxC+O7a81QitJtV+&` zt4h+un0lNm9H%|54A=w(Ez)HlQmxgDA6Q+37oTfK*)b<483`lb&sy?_%-r1M(_$&! zE32%RP(3p&ASgqE)5vPt!)MZ@NgkCA<>$0Cx49W?|HL!%w&c{Jqdqj$kj}~K}tD5mjgnz1g z1qKlf>`c1&B_$$}R7YzZLzRZmdb+tvJR(Td~j{HZQ&TZ|fy|8saMnrChjrV8RFX^!<%r zXw3h)?6!S9(2_K6aD~CNG}2s;BYb_nd>?`JYCwrM$(sNqg+1DPnVhoq3ILp&mun*? GEbAYGN=h~W literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Group_1597880534@3x.png b/MusicPlayer/Assets.xcassets/Positive/Center/Offline_Songs_logo.imageset/Group_1597880534@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f35fa09cc27dc971f3f3c269041ef2992ca1940e GIT binary patch literal 3063 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 FV1ia4M*9E& literal 0 HcmV?d00001 diff --git a/MusicPlayer/Assets.xcassets/Positive/Search/Tag_Delete'logo.imageset/Frame@3x.png b/MusicPlayer/Assets.xcassets/Positive/Search/Tag_Delete'logo.imageset/Frame@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..183bee95c5d15b0b5aaddca47ddd704760f62401 GIT binary patch literal 2093 zcmV+|2-5e7P)Px+=Sf6CRCr$PoO#FRuuxeX+pB2LJJP=bQO*FFwou14&w}qtRBpoUV zYb4W!f+daXmAmx>Amw#F*Ozprq@$wwb0s}&W(5(yvyAgpasrwrKE zf;>1UNIE8}Vdk0HJszM3TPU-CWcmQ~5sS_Q#k(1bD<2|h-C!_5ntOFC==OFN^xtX+ zQYU~Y)Nb`_rtpSy&1{$lXk-gbP?~I$3mFTgUAJ0yIS$>q*LuNt7Pu(@F_k%Yo7wzU z_w57_?lV*pStAr;N?zV?(&C~lK*Q?-Ot0D%b=(Wx?rPU5=mZc}K%qwkOHf=Dg_r%F zuf%b;6{KSpbIxJDxLW8kv@9-;t~6TGPXmTvH+tg!^E(x;$!Xr#Aa_Jy3^Oxooq>_M z^lShsc%=i8VDmC}NI zyOe&>(SAWo|K{~}t%lU`p;4`@0I?^3ThOBD*lKXjC5>Md0HT-N^*XMK6;zoU0^Qd_ zF;(f*@q=PP&{f#A2zx|y&1B0Z~G5V7YHNe2ZNn%T|1d#XjGj*l$pMoD`G-+e4;R4Q?zj0KUs&i0J4{Qke1 z+08|7CiQ_+$43C&@A*Xe*HbdUo=;`WCIiIXoO2K{&xrQFZYeU~)CW!-AFRV5S^?1V zud+?Xm(A?8qL&!|UKXIkGdc_V;^C$u?2T!i6i9vG)bRm8Q1`A5bb-@2#?06QOOmnx z!5bt{6R>lcY-X@e+e+#Kr;ZN*f=$bUI2U$;N17R&#v~~V5GKsNeaP+v2S*!$=PZ6}vc&Fs-sa2Q|8g1jQ|1c^hrKmvHd%%GFB z72o5@m0wWAPAxGW*Md_JviP4dv-kTA5RCcZks?rgNB8rnK={y}{zrib;UX6nsY9|d zq!i)2v;}!9f+rXjjQBXga8{YORdtSYZU@h<4c%+1nLSnZ+EoF9kBGmAt&bUIHlnOT zRpvS8Zp!F>dq&rI%*^0smy)Ugk&P~s1nn*qh~012McOQSGEZEmc!k-Y3rT<@4%BB! z>#y*BRRLl?Z3XHMID@(1YNPy#oZ8v%QQU!GO7G{=Xlap?M?b9cqACI8;}EGED}pZ= zzCHRbPn#L_QP0FVN4;}}=Vgb_Ls1C(zS5DY$^dzg$Y>O2>_UV>eKX!4gj;N8wGlae z#yVKi`7L;qoCBfvQz~=S=3IUJMfT5kYcNnNmMCJx?Ov+oy5>!h%DA!%bUb zW*uf*iv@WrA{LPHsY60eBR>;|h)Xo~q3KRbZ96#UDEwH2a*Ak2-xH7ZXP@o4jDmm>n47bA$q=f=u{ z5zpfoCkJIHPU}AEu9`>E4em$__1)B1milIT4p0!A z@|Z$9f<6)yn9V(4fJQdrpAI1OlyDuag80OldFm?S3|2Fz?sH-7fa@DTkyYjYiULsh zn@|34DV8NgpZNfaGJ(&Meu2`{ES74*gFgO$v7kW*rLmxdH`G{A0ay({4L}M0)MDtr Xx$jqYZhZCu00000NkvXXu0mjf*|O`2 literal 0 HcmV?d00001 diff --git a/MusicPlayer/Info.plist b/MusicPlayer/Info.plist index d8c5b91..8f0456f 100644 --- a/MusicPlayer/Info.plist +++ b/MusicPlayer/Info.plist @@ -20,5 +20,21 @@ "Musicoo" needs to obtain your location information in order to refine the preview music information provided to you! NSLocationAlwaysAndWhenInUseUsageDescription "Musicoo" needs to obtain your location information in order to refine the preview music information provided to you! + CFBundleURLTypes + + + CFBundleURLSchemes + + myapp + + CFBundleURLName + com.lux.musicplayer.MusicPlayer + + + UIBackgroundModes + + fetch + backgroundFetch + diff --git a/MusicPlayer/MP/Common/DataBase/MusicPlayer.xcdatamodeld/MusicPlayer.xcdatamodel/contents b/MusicPlayer/MP/Common/DataBase/MusicPlayer.xcdatamodeld/MusicPlayer.xcdatamodel/contents index 95fb2fa..47696a0 100644 --- a/MusicPlayer/MP/Common/DataBase/MusicPlayer.xcdatamodeld/MusicPlayer.xcdatamodel/contents +++ b/MusicPlayer/MP/Common/DataBase/MusicPlayer.xcdatamodeld/MusicPlayer.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -10,26 +10,33 @@ + + + - + + - - + + + + + diff --git a/MusicPlayer/MP/Common/Extension(扩展)/AVPlayerItem.swift b/MusicPlayer/MP/Common/Extension(扩展)/AVPlayerItem.swift new file mode 100644 index 0000000..90c31e3 --- /dev/null +++ b/MusicPlayer/MP/Common/Extension(扩展)/AVPlayerItem.swift @@ -0,0 +1,22 @@ +// +// AVPlayerItem.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/23. +// + +import AVFoundation +import ObjectiveC + +private var playerItemContext: UInt8 = 0 + +extension AVPlayerItem { + var uniqueID: String? { + get { + return objc_getAssociatedObject(self, &playerItemContext) as? String + } + set { + objc_setAssociatedObject(self, &playerItemContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } +} diff --git a/MusicPlayer/MP/Common/Extension(扩展)/Notification.swift b/MusicPlayer/MP/Common/Extension(扩展)/Notification.swift index a897bd8..28ca76e 100644 --- a/MusicPlayer/MP/Common/Extension(扩展)/Notification.swift +++ b/MusicPlayer/MP/Common/Extension(扩展)/Notification.swift @@ -79,6 +79,10 @@ extension NotificationCenter{ case pup_player_vc ///播放器页面更新 case positive_player_reload + ///播放器预加载成功 + case positive_asset_successfully + ///用户手动调整了进度 + case positive_player_seek ///用户切换播放器播放方式 case player_type_switch ///用户清空了歌单 @@ -87,6 +91,10 @@ extension NotificationCenter{ case net_switch_notReachable ///网络状态切换-网络可用 case net_switch_reachable + ///b面pushj时 + case positive_nav_push + ///b面Pop时 + case positive_nav_pop } } } diff --git a/MusicPlayer/MP/Common/Extension(扩展)/String.swift b/MusicPlayer/MP/Common/Extension(扩展)/String.swift index 7aa1a80..ce85709 100644 --- a/MusicPlayer/MP/Common/Extension(扩展)/String.swift +++ b/MusicPlayer/MP/Common/Extension(扩展)/String.swift @@ -20,6 +20,24 @@ extension String { // 类type return anyClass } + //MARK: - 字符串扩展 + ///根据宽度跟字体,计算文字的高度 + func textAutoHeight(width:CGFloat, font:UIFont) ->CGFloat{ + let string = self as NSString + let origin = NSStringDrawingOptions.usesLineFragmentOrigin + let lead = NSStringDrawingOptions.usesFontLeading + let ssss = NSStringDrawingOptions.usesDeviceMetrics + let rect = string.boundingRect(with:CGSize(width: width, height:0), options: [origin,lead,ssss], attributes: [NSAttributedString.Key.font:font], context:nil) + return rect.height + } + ///根据高度跟字体,计算文字的宽度 + func textAutoWidth(height:CGFloat, font:UIFont) ->CGFloat{ + let string = self as NSString + let origin = NSStringDrawingOptions.usesLineFragmentOrigin + let lead = NSStringDrawingOptions.usesFontLeading + let rect = string.boundingRect(with:CGSize(width:0, height: height), options: [origin,lead], attributes: [NSAttributedString.Key.font:font], context:nil) + return rect.width + } } extension Range where Bound == String.Index { func toNSRange(in string: String) -> NSRange { diff --git a/MusicPlayer/MP/Common/Macro(宏定义与全局量)/Macro.swift b/MusicPlayer/MP/Common/Macro(宏定义与全局量)/Macro.swift index fe29db8..34bc674 100644 --- a/MusicPlayer/MP/Common/Macro(宏定义与全局量)/Macro.swift +++ b/MusicPlayer/MP/Common/Macro(宏定义与全局量)/Macro.swift @@ -55,6 +55,22 @@ typealias ActionBlock = () -> Void? var MPSideA_ModalType:MPSideA_PresentModal = .Timer ///B面全局模态弹出类型 var MPPositive_ModalType:MPPositive_PresentModal = .PlayerList +///沙盒文件 +let DocumentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] +///获取沙盒下载路径 +func getDocumentsFileURL(_ videoID: String) -> String? { + // 获取Documents目录的URL + let documentsDirectoryURL = DocumentsURL.appendingPathComponent("Downloads") + // 根据videoId构建文件完整URL + let fileURL = documentsDirectoryURL.appendingPathComponent("\(videoID).mp4") + //检索是否存在 + if FileManager.default.fileExists(atPath: fileURL.path) == true { + //存在 + return fileURL.absoluteString + }else { + return nil + } +} ///转时分值 func setTimesToMinSeconds(_ time:TimeInterval) -> String { //设置分钟 diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MPTableManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MPTableManager.swift deleted file mode 100644 index 4bb4649..0000000 --- a/MusicPlayer/MP/Common/Tool(工具封装)/MPTableManager.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// MPTableManager.swift -// MusicPlayer -// -// Created by Mr.Zhou on 2024/4/7. -// - -import UIKit -///tableViewCell配置闭包(index-索引路径,tableView-当前表格,在VC中实现cell实体) -typealias TableViewCellConfigureBlock = (_ index:IndexPath, _ tableView:UITableView) -> UITableViewCell -///tableViewCell点击闭包(index-索引路径,tableView-当前表格,在VC中实现点击方法) -typealias TableViewCellDidSelectBlock = (_ index:IndexPath, _ tableView:UITableView) -> Void -///tableView通用控制器 -class MPTableManager:NSObject { - ///包含组行的数据组 - private var sectionAndItems:[[Any]]? - ///cell注册id组(与数据组组数相同) - private var cellIdentifiers:[String]? - //配置闭包 - private var configureCellBlock:TableViewCellConfigureBlock! - //选中闭包 - private var didSelectBlock:TableViewCellDidSelectBlock? - - /// 自定义UITableView控制器初始化方法 - /// - Parameters: - /// - sectionItems: 包含组行的二维数组数据 - /// - cellIdentifier: cell注册ID组(与sectionItems中的组数相同) - /// - configureCellBlock: cell配置闭包 - /// - didSelectBlock: cell点击事件闭包 - init(_ sectionItems: [[Any]]?, cellIdentifiers: [String]?, configureCellBlock: TableViewCellConfigureBlock?, didSelectBlock: TableViewCellDidSelectBlock?) { - super.init() - self.sectionAndItems = sectionItems - self.cellIdentifiers = cellIdentifiers - self.configureCellBlock = configureCellBlock - self.didSelectBlock = didSelectBlock - } - //获取索引指定的数据 - private func item(_ indexPath: IndexPath) -> Any? { - if let items = sectionAndItems?[indexPath.section] { - return items[indexPath.row] - }else { - return nil - } - } - //为tableView注册delegate,datasource,以及cell,自适应高度 - func handleTableViewDataSourceAndDelegate(_ tableView: UITableView?, rowHeight:CGFloat = 0) { - guard let table = tableView else { - print("TableView is null") - return - } - table.dataSource = self - table.delegate = self - table.rowHeight = rowHeight != 0 ? rowHeight:UITableView.automaticDimension - table.estimatedRowHeight = 200 - guard cellIdentifiers != nil else { - return - } - cellIdentifiers!.forEach { item in - //判断未注册 - guard table.dequeueReusableCell(withIdentifier: item) == nil else { return } - //判断是否存在nib文件 - if let _ = Bundle.main.path(forResource: item, ofType: "nib") { - //nib注册 - table.register(.init(nibName: item, bundle: nil), forCellReuseIdentifier: item) - }else { - //cell注册 - table.register(item.classFromString().self, forCellReuseIdentifier: item) - } - } - } -} -//MARK: - tableView的实现 -extension MPTableManager:UITableViewDataSource, UITableViewDelegate { - //返回组 - func numberOfSections(in tableView: UITableView) -> Int { - guard sectionAndItems != nil else { - return 0 - } - return sectionAndItems!.count - } - //返回行 - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let items = sectionAndItems?[section] else { - return 0 - } - return items.count - } - //实现cell - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = configureCellBlock(indexPath, tableView) - return cell - } - //点击事件 - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard didSelectBlock != nil else { - return - } - didSelectBlock!(indexPath, tableView) - } -} diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_AVURLAsset.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_AVURLAsset.swift new file mode 100644 index 0000000..e29c844 --- /dev/null +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_AVURLAsset.swift @@ -0,0 +1,501 @@ +// +// MP_AVURLAsset.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/23. +// + +import UIKit +import AVFoundation +extension URL { + func convertCustomSchemeURL(_ scheme: String) -> URL? { + var components = URLComponents(url: self, resolvingAgainstBaseURL: false) + components?.scheme = scheme + return components?.url + } + +} +///媒体加载协议 +@objc protocol MP_AVPlayerItemDelegate { + ///当媒体项目初次缓存后 + @objc func playerItemReadyToPlay(_ playerItem: MP_AVPlayerItem) + ///当媒体项目收到新缓存后 + @objc func playerItem(_ playerItem: MP_AVPlayerItem, progress:Float) + ///当媒体项目完全加载后 + @objc func playerItem(_ playerItem: MP_AVPlayerItem, didFinishLoadingData data:Data) + ///当媒体项目加载数据中断(比如断网了),导致停止播放时 + @objc func playerItemPlaybackStalled(_ playerItem: MP_AVPlayerItem) + ///当媒体项目加载错误时调用。 + @objc func playerItem(_ playerItem: MP_AVPlayerItem, loadingError error:Error) +} +///自定义媒体项目 +class MP_AVPlayerItem: AVPlayerItem { + ///拦截协议 + var resourceLoaderDelegate:MP_ResourceLoaderDelegate! + ///地址路径 + fileprivate var url:URL + ///原生Scheme + fileprivate let initialScheme:String? + ///自定义文件扩展名 + fileprivate var customFileExtension:String? + ///拦截式scheme + fileprivate let customScheme = "myapp" + ///媒体项目标题 + fileprivate var title:String = "Random" + ///媒体ID + fileprivate var videoId:String = "Random" + ///媒体资产 + fileprivate var resourceAsset:MP_AVURLAsset! + + ///媒体项目协议 + weak var delegate:MP_AVPlayerItemDelegate? + + ///自定义初始化 + init(url: URL, bitrate:Int64, customFileExtension: String? = "mp4", title:String?, videoId:String?) { + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let scheme = components.scheme, + var urlWithCustomScheme = url.convertCustomSchemeURL(customScheme) else { + fatalError("不支持没有scheme的URL") + } + self.url = url + self.initialScheme = scheme + //将扩展文件名赋予自定义scheme + if let stringPath = customFileExtension { + //先删除扩展名(网络文件路径一般没有) + urlWithCustomScheme.deletePathExtension() + //扩展路径 + urlWithCustomScheme.appendPathExtension(stringPath) + //更新自定义文件扩展名 + self.customFileExtension = stringPath + } + if let title = title, let videoId = videoId { + //更新标题 + self.title = title + self.videoId = videoId + } + resourceLoaderDelegate = .init(bitrate) + resourceAsset = MP_AVURLAsset(url: urlWithCustomScheme, delegate: resourceLoaderDelegate, title: title ?? "") + //生成一个媒体资产 + super.init(asset: resourceAsset, automaticallyLoadedAssetKeys: nil) + //为拦截协议传入指定的playerItem + resourceLoaderDelegate.playItem = self + //kvo监听项目状态 + addObserver(self, forKeyPath: "status", options: [.old,.new], context: nil) + //kvo监听项目加载值 + addObserver(self, forKeyPath: "loadedTimeRanges", options: [.old,.new], context: nil) + //kvo监听项目准备情况 + addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.old,.new], context: nil) + //kvo监听项目报错 + addObserver(self, forKeyPath: "error", options: [.old,.new], context: nil) + NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self) + } + //MARK: - KVO实现 + override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + guard let keyPath = keyPath else { + return + } + //根据keyPath检索 + switch keyPath { + case "status"://playerItem状态 + if let statuValue = change?[.newKey] as? Int, statuValue == AVPlayerItem.Status.readyToPlay.rawValue { + //判断当前播放器是否在播放当前音乐中 + print("当前音乐-\(title) 已经准备好播放") + } + case "loadedTimeRanges"://当前缓冲进度 + guard self.videoId == MP_PlayerManager.shared.loadPlayer.currentVideo?.song.videoId else {return} + let timeRanges = self.loadedTimeRanges.map({$0.timeRangeValue}) + //获取当前播放Item的缓冲值组 + if let first = timeRanges.first { + //获取开始时间的秒数 + let startSeconds = first.start.seconds + //获取缓冲区的持续时间 + let durationSeconds = first.duration.seconds + //计算当前缓冲总时间 + let bufferedSeconds = startSeconds + durationSeconds + //获取当前播放音乐资源的最大时间值 + let maxDuration = CMTimeGetSeconds(self.duration) + let progress = bufferedSeconds/maxDuration + //传递缓存值 + delegate?.playerItem(self, progress: Float(progress)) + } + case "playbackLikelyToKeepUp"://是否存在足够的数据开始播放 + if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true { + //有足够的数据来支持播放 + delegate?.playerItemReadyToPlay(self) + }else { + //没有足够的数据支持播放 + delegate?.playerItemPlaybackStalled(self) + } + case "error": + if let error = change?[.newKey] as? Error { + print("当前音乐-\(title) 未做好准备播放,失败原因是\(error)") + delegate?.playerItem(self, loadingError: error) + } + default: + break + } + } + //playerItem突然停止了 + @objc func playbackStalledHandler() { + delegate?.playerItemPlaybackStalled(self) + } + + deinit { + print("已销毁\(title)") + NotificationCenter.default.removeObserver(self) + removeObserver(self, forKeyPath: "status") + removeObserver(self, forKeyPath: "loadedTimeRanges") + removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") + removeObserver(self, forKeyPath: "error") + //终止网络请求 + resourceLoaderDelegate.session?.invalidateAndCancel() + //清理资产空间 + resourceAsset = nil + } +} +///自定义媒体资产 +class MP_AVURLAsset: AVURLAsset { + init(url URL: URL, delegate:MP_ResourceLoaderDelegate, title:String) { + super.init(url: URL, options: nil) + self.resourceLoader.setDelegate(delegate, queue: .main) + //对该Asset实现预加载,以让Asset触发resourceLoaderdelegate + // 加载关键的播放属性 + let keys = ["playable"] + self.loadValuesAsynchronously(forKeys: keys) { + [weak self] in + // 检查加载属性的结果 + for key in keys { + var error: NSError? + let status = self?.statusOfValue(forKey: key, error: &error) + if status == .loaded { + print("开始对\(title)的预加载") + } else { + // 处理加载失败的情况 + print("无法加载 \(key): \(String(describing: error))") + //一般是网络数据问题,需要重新请求一遍 + } + } + } + } +} +///媒体资源加载代理 +class MP_ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate { + //判断缓存空间的数据是否完整 + private var iscompleted:Bool? + // 从内存播放时需要 + var mimeType: String? + //比特率 + var bitrate:Int64 + //网络会话 + var session: URLSession? + //媒体数据 + var mediaData: Data? + //媒体数据块 + var mediaDataBlocks: [MediaDataBlock]? + //响应体 + var response: URLResponse? + //活跃的加载请求 + var pendingRequests = Set() + //缓存中的最长长度值 + var maxCount:Int64? + //缓存情况 + var progress:Float = 0 + weak var playItem: MP_AVPlayerItem? + //最后加载范围 + private var lastRequestedEndOffset:Int64? + + init(_ bitrate:Int64) { + self.bitrate = bitrate + super.init() + //添加关于用手动seek的监听 + NotificationCenter.notificationKey.add(observer: self, selector: #selector(seekAction(_ :)), notificationName: .positive_player_seek) + } + + func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { + guard let initialUrl = playItem?.url else { + fatalError("internal inconsistency") + } + //判断session是否存在 + if session == nil { + //判断加载数据方法 + if let media = MP_CacheManager.shared.data(forKey: playItem?.videoId ?? "") { + print("缓存中存在\(playItem?.title ?? ""),取出中") + //从缓存中去获取数据,并覆盖响应数据 + mediaData = media.data + mediaDataBlocks = media.dataBlocks + //判断是否完整 + if media.isComplete == false { + print("\(playItem?.title ?? "")数据不完整,开始网络请求相关数据") + maxCount = media.maxCount + //缓存中的数据是不完整的,继续网络请求 + continuationDataRequest(with: initialUrl, count: Int64(media.data.count)) + iscompleted = false + }else { + iscompleted = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + guard let self = self else {return} + if MP_PlayerManager.shared.loadPlayer.currentVideo?.song.videoId == playItem?.videoId { + playItem?.delegate?.playerItem(playItem!, didFinishLoadingData: media.data) + } + } + } + }else { + print("开始网络请求\(playItem?.title ?? "")相关数据") + //初次请求 + startDataRequest(with: initialUrl) + } + }else { + } + pendingRequests.insert(loadingRequest) +// processPendingRequests() + return true + + } + @objc private func seekAction(_ sender:Notification) { + if playItem?.videoId == MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId { + if let progress = sender.object as? Double { + let requestedOffset = progress * Double(maxCount ?? 0) + // 测试是否发送网络请求 + if requestedOffset > Double(mediaData?.count ?? 0) { + print("用户滚动到了当前缓存范围外") + // 用户seek位置在媒体数据范围内,假设这里已经有了初始URL + let initialUrl = playItem?.url + continuationDataRequest(with: initialUrl!, count: Int64(requestedOffset)) + } + } + } + } + + + /// 开始网络请求 + /// - Parameters: + /// - url: 网络请求地址 + func startDataRequest(with url: URL) { + //取消之前的请求方法 + session?.invalidateAndCancel() + let configuration = URLSessionConfiguration.default + configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData + session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) + session?.dataTask(with: url).resume() + } + ///继续请求网络资源(因各种原因中断时) + /// - Parameters: + /// - url: 网络请求地址 + /// - data: 数据内容 + func continuationDataRequest(with url: URL, count:Int64) { + if session == nil { + let configuration = URLSessionConfiguration.default + configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData + session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) + }else { + //取消之前的请求方法 + session?.getAllTasks(completionHandler: { tasks in + //取消所有方法 + tasks.forEach({$0.cancel()}) + }) + } + //设置一个网络请求 + var request = URLRequest(url: url) + //设置请求头内容 + let bytes = "bytes=\(count)-" + request.addValue(bytes, forHTTPHeaderField: "Range") + request.addValue(url.host ?? "", forHTTPHeaderField: "Host") + request.addValue("video/mp4", forHTTPHeaderField: "Accept") + session?.dataTask(with: request).resume() + } + func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) { + pendingRequests.remove(loadingRequest) + } + + // MARK: URLSession + ///网络请求响应体内容 + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + guard response.mimeType?.contains("video") == true else { + print("\(playItem?.title ?? "")网络地址不可用,响应类型是\(response.mimeType ?? "")") + //重新请求数据 + MP_NetWorkManager.shared.requestPlayer(playItem?.videoId ?? "", playlistId: "") {[weak self] resourceUrls, coverUrls in + //只需要重新配置第一条网络资源地址 + guard let self = self else {return} + print("\(playItem?.title ?? "")重新加载一次") + let url = URL(string: resourceUrls.0.first ?? "")! + playItem?.url = url + if mediaData == nil { + startDataRequest(with: url) + }else { + continuationDataRequest(with: url, count: Int64(mediaData!.count)) + } + } + return + } + completionHandler(Foundation.URLSession.ResponseDisposition.allow) + if mediaData == nil { + mediaData = Data() + mediaDataBlocks = [] + } + self.response = response + processPendingRequests() + } + ///网络请求数据更新 + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + guard let response = dataTask.response as? HTTPURLResponse, + let contentRange = response.allHeaderFields["Accept-Ranges"] as? String else { + print("\(playItem?.title ?? "")数据更新失败") + return + } + //从 Content-Range 标头中提取范围信息 + let rangeInfo = extractRangeInfo(contentRange) + //使用范围的起始偏移量和接收到的数据创建一个 DataSegment + let dataSegment = MediaDataBlock(offset: (rangeInfo?.start ?? 0), data: data) + mediaData?.append(data) + mediaDataBlocks?.append(dataSegment) + if mediaData != nil { + //存入缓存数据 + MP_CacheManager.shared.cacheData(mediaData!, dataBlocks: mediaDataBlocks!, forKey: MP_PlayerManager.shared.loadPlayer.currentVideo?.song.videoId ?? "", isComplete: false, maxCount: self.response?.expectedContentLength ?? 0) + } + processPendingRequests() + //更新最后一次加载完成的范围量 + self.lastRequestedEndOffset = Int64(data.count) + } + //解析 Content-Range 标头以提取字节范围 + private func extractRangeInfo(_ contentRange: String) -> (start: Int64, end: Int64)? { + // 正则表达式用于匹配 "bytes start-end/total" 格式 + let pattern = "bytes (\\d+)-(\\d+)/(\\d+|\\*)" + // 尝试创建正则表达式 + guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { + return nil + } + // 在 Content-Range 标头中查找匹配项 + let nsRange = NSRange(contentRange.startIndex..(pendingRequests.compactMap { + self.fillInContentInformationRequest($0.contentInformationRequest) + if self.haveEnoughDataToFulfillRequest($0.dataRequest!) { + $0.finishLoading() + return $0 + } + return nil + }) + // 从待处理的请求中删除已完成的请求 + _ = requestsFulfilled.map { self.pendingRequests.remove($0) } + + } + ///填写contentInformationRequest + func fillInContentInformationRequest(_ contentInformationRequest: AVAssetResourceLoadingContentInformationRequest?) { + //如果缓存数据加载完全了 + if iscompleted == true { + contentInformationRequest?.contentType = "video/mp4" + self.maxCount = Int64(mediaData!.count) + contentInformationRequest?.contentLength = Int64(mediaData!.count) + contentInformationRequest?.isByteRangeAccessSupported = true + return + } + //通过获取响应体,填写contentInformationRequest + if let responseUnwrapped = response { + contentInformationRequest?.contentType = responseUnwrapped.mimeType + self.maxCount = responseUnwrapped.expectedContentLength + contentInformationRequest?.contentLength = responseUnwrapped.expectedContentLength + contentInformationRequest?.isByteRangeAccessSupported = true + }else if let maxCount = maxCount { + contentInformationRequest?.contentType = "video/mp4" + contentInformationRequest?.contentLength = maxCount + contentInformationRequest?.isByteRangeAccessSupported = true + } + } + ///判读请求是否有足够的数据 + func haveEnoughDataToFulfillRequest(_ dataRequest: AVAssetResourceLoadingDataRequest) -> Bool { + let requestedOffset = Int64(dataRequest.requestedOffset) + let requestedLength = dataRequest.requestedLength + let currentOffset = Int64(dataRequest.currentOffset) + // 需要构建一个包含请求范围数据的Data对象 + var responseData = Data() + + // 遍历存储的数据块,查找并拼接满足请求范围的数据块 + // 假设数据块已经按照偏移量升序排序 + for item in (mediaDataBlocks ?? []).sorted(by: { $0.offset < $1.offset }) { + // 判断当前数据块是否在请求范围内 + if item.offset < currentOffset + Int64(requestedLength) && item.offset + Int64(item.data.count) > currentOffset { + // 计算数据块在请求范围内的部分,并追加到responseData + let start = max(currentOffset, item.offset) - item.offset + let end = min(currentOffset + Int64(requestedLength), item.offset + Int64(item.data.count)) - item.offset + if start < end { // 确保范围有效 + responseData.append(item.data.subdata(in: Int(start)..= requestedLength { + // 使用请求范围内的数据回应dataRequest + dataRequest.respond(with: responseData) + return true + } + + // 得到的数据长度不满足请求长度,返回false + return false +// guard let songDataUnwrapped = mediaData, +// songDataUnwrapped.count > currentOffset else { +// return false +// } +// let bytesToRespond = min(songDataUnwrapped.count - currentOffset, requestedLength) +// let dataToRespond = songDataUnwrapped.subdata(in: Range(uncheckedBounds: (currentOffset, currentOffset + bytesToRespond))) +// dataRequest.respond(with: dataToRespond) +// return songDataUnwrapped.count >= requestedLength + requestedOffset + + } + + deinit { + NotificationCenter.default.removeObserver(self) + session?.invalidateAndCancel() + mediaData = nil + mediaDataBlocks = nil + } + +} +///媒体数据模型 +struct MediaDataBlock: Codable { + ///数据偏移量 + var offset:Int64 + ///数据块内容 + var data:Data +} diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_CacheManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_CacheManager.swift new file mode 100644 index 0000000..f3a3e43 --- /dev/null +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_CacheManager.swift @@ -0,0 +1,143 @@ +// +// MP_CacheManager.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/23. +// + +import Foundation +///缓存实体资源 +class CachedMedia: NSObject, Codable { + //数据 + let data: Data + //数据块 + let dataBlocks:[MediaDataBlock] + //是否完整 + let isComplete: Bool + //最大长度 + let maxCount:Int64 + init(data: Data, dataBlocks:[MediaDataBlock], isComplete: Bool, maxCount:Int64) { + self.data = data + self.dataBlocks = dataBlocks + self.isComplete = isComplete + self.maxCount = maxCount + } +} +///缓存管理工具 +class MP_CacheManager { + // 单例模式,提供全局访问点 + static let shared = MP_CacheManager() + // 缓存实体,字典形式,键值1为videoID,键值2为对应的资源数据 + private let memoryCache = NSCache() + //文件管理器 + private let fileManager = FileManager.default + //缓存文件地址 + private let cacheDirectory: URL + //缓存专用线程 + private var cacheQueue = DispatchQueue(label: "com.MP_CacheManager.cacheQueue") + //缓存专用 + private var cacheOperations: [String: DispatchWorkItem] = [:] + //专用节流时间间隔 + private let throttleInterval: TimeInterval = 1.0 + //固定缓存时间 + private let expirationInterval: TimeInterval = 86400 // 24小时 + private init() { + //获取全部文件地址 + let urls = fileManager.urls(for: .cachesDirectory, in: .userDomainMask) + //设置缓存文件地址 + cacheDirectory = urls[0].appendingPathComponent("MyAssetCacheManager") + //检查缓存文件地址是否存在 + if !fileManager.fileExists(atPath: cacheDirectory.path) { + //不存在就创建一个 + try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true, attributes: nil) + } + } + + /// 保存缓存数据 + /// - Parameters: + /// - data: 数据本身 + /// - key: 使用videoId作为对应的键值 + func cacheData(_ data: Data, dataBlocks:[MediaDataBlock], forKey key: String, isComplete: Bool = false, maxCount:Int64) { + // 在你的 cacheQueue 里执行操作来确保线程安全 + cacheQueue.async { [weak self] in + // 取消这个 key 的任何现有缓存操作 + self?.cacheOperations[key]?.cancel() + + // 创建一个新的缓存操作 + let operation = DispatchWorkItem { + self?.performCacheData(data, dataBlocks: dataBlocks, forKey: key, isComplete: isComplete, maxCount: maxCount) + // 甚至删除值也包含在cacheQueue的异步调用中 + self?.cacheQueue.async { + self?.cacheOperations.removeValue(forKey: key) + } + } + + // 把 operation 保存进字典 + self?.cacheOperations[key] = operation + + // 安排 operation 在节流间隔后运行 + self?.cacheQueue.asyncAfter(deadline: .now() + self!.throttleInterval, execute: operation) + } + } + //保存缓存数据 + private func performCacheData(_ data: Data, dataBlocks:[MediaDataBlock], forKey key: String, isComplete: Bool = false, maxCount:Int64) { + let contentToCache = CachedMedia(data: data, dataBlocks: dataBlocks, isComplete: isComplete, maxCount: maxCount) + memoryCache.setObject(contentToCache, forKey: key as NSString) + //文件路径 + let fileURL = self.cacheDirectory.appendingPathComponent(key) + //临时文件 + let tempURL = fileURL.appendingPathExtension("tmp") + let encoder = JSONEncoder() + do { + let newData = try encoder.encode(contentToCache) + // 写入到文件 + try newData.write(to: tempURL, options: .atomicWrite) + // 检查目标位置是否已经存在文件,如果存在,则删除 + if self.fileManager.fileExists(atPath: fileURL.path) { + try self.fileManager.removeItem(at: fileURL) + } + // 然后将临时文件移动到最终位置 + try self.fileManager.moveItem(at: tempURL, to: fileURL) + // 设置过期时间元数据 + let expirationDate = Date().addingTimeInterval(self.expirationInterval) + try self.fileManager.setAttributes([.modificationDate: expirationDate], ofItemAtPath: fileURL.path) + } catch { + // 如果发生错误,删除临时文件 + try? self.fileManager.removeItem(at: tempURL) + print("无法将密钥数据写入磁盘 \(key) - 错误: \(error)") + } + } + /// 取出数据 + /// - Parameter key: 使用videoId作为对应的键值 + /// - Returns: 返回的数据 + func data(forKey key: String) -> CachedMedia? { + if let cachedContent = memoryCache.object(forKey: key as NSString){ + return cachedContent + } + return cacheQueue.sync { + [weak self] in + guard let self = self else { return nil } + let fileURL = self.cacheDirectory.appendingPathComponent(key) + guard let jsonData = try? Data(contentsOf: fileURL) else { return nil } + // 将从磁盘中读取到的 JSON 数据反序列化为 CachedMedia 对象 + let decoder = JSONDecoder() + do{ + let cachedContent = try decoder.decode(CachedMedia.self, from: jsonData) + // 检查数据是否过期(同时可以在此处加入完整性检查) + let attributes = try fileManager.attributesOfItem(atPath: fileURL.path) + guard let modificationDate = attributes[.modificationDate] as? Date else { return nil } + if Date().timeIntervalSince(modificationDate) < expirationInterval{ + // 数据未过期,返回数据并更新内存缓存 + memoryCache.setObject(cachedContent, forKey: key as NSString) + return cachedContent + } else { + // 数据已过期,删除缓存文件 + try fileManager.removeItem(at: fileURL) + } + } catch { + print("读取缓存数据失败 \(key) - 错误: \(error)") + } + return nil + } + } +} diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_DownloadManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_DownloadManager.swift index 1de0afc..0ab6d80 100644 --- a/MusicPlayer/MP/Common/Tool(工具封装)/MP_DownloadManager.swift +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_DownloadManager.swift @@ -7,89 +7,96 @@ import Foundation import Foundation -import Alamofire - -class DownloadManager: NSObject, URLSessionDownloadDelegate { +import Tiercel +class DownloadManager: NSObject { static let shared = DownloadManager() - var session: URLSession! + var session: SessionManager! var progressHandlers: [URL: (CGFloat) -> Void] = [:] - var completionHandlers: [URL: (Result) -> Void] = [:] + var completionHandlers: [URL: (Result) -> Void] = [:] var downloadTasks: [URL: URLSessionDownloadTask] = [:] var progressStorage: [URL: CGFloat] = [:] // 新增进度存储 - + var songHandlers:[URL: MPPositive_SongItemModel] = [:] private override init() { super.init() - let configuration = URLSessionConfiguration.background(withIdentifier: "com.yourApp.backgroundDownload") - session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil) + var configuration = SessionConfiguration() + configuration.timeoutIntervalForRequest = 60 + configuration.maxConcurrentTasksLimit = 6 + configuration.allowsCellularAccess = true + session = SessionManager("com.yourApp.backgroundDownload", configuration: configuration) } - - func downloadVideo(from url: URL, videoId: String, progressHandler: @escaping (CGFloat) -> Void, completion: @escaping (Result) -> Void) { - let downloadTask = session.downloadTask(with: url) + func downloadVideo(from url: URL, song:MPPositive_SongItemModel, progressHandler: @escaping (CGFloat) -> Void, completion: @escaping (Result) -> Void) { progressHandlers[url] = progressHandler completionHandlers[url] = completion - downloadTasks[url] = downloadTask - downloadTask.resume() - } + songHandlers[url] = song + let downloadTask = session.download(url, headers: ["Accept-Encoding": "gzip, deflate"]) + //配置进度条 + downloadTask?.progress(handler: { [weak self] (task) in + guard let self = self else {return} + progressHandlers[task.url]?(task.progress.fractionCompleted) + progressStorage[task.url] = task.progress.fractionCompleted + }) + //配置任务完成事件 + downloadTask?.success(handler: { [weak self] (task) in + guard let self = self else {return} + //任务下载成功,进行文件转移 + let originalURL = task.url + //任务下载地址 + let filePathUrl:URL = URL(fileURLWithPath: task.filePath) + print("任务下载地址:\(filePathUrl)") + let downloadsURL = DocumentsURL.appendingPathComponent("Downloads") + if !FileManager.default.fileExists(atPath: downloadsURL.path) { + do { + try FileManager.default.createDirectory(at: downloadsURL, withIntermediateDirectories: true, attributes: nil) + } catch { + completionHandlers[originalURL]?(.failure(error)) + return + } + } + let fileURL = downloadsURL.appendingPathComponent("\(MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId ?? "").mp4") + do { + try FileManager.default.moveItem(at: filePathUrl, to: fileURL) + //回调VideoID + completionHandlers[originalURL]?(.success(songHandlers[originalURL]!)) + progressStorage[originalURL] = nil // 清除已完成任务的进度记录 + } catch { + completionHandlers[originalURL]?(.failure(error)) + } + }).failure(handler: { [weak self] (task) in + guard let self = self else {return} + //任务下载失败 + let originalURL = task.url + if let error = task.error { + completionHandlers[originalURL]?(.failure(error)) + } + }) + } func getProgress(for url: URL) -> CGFloat? { return progressStorage[url] } - - // URLSessionDownloadDelegate methods - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - guard let originalURL = downloadTask.originalRequest?.url else { return } - let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - let downloadsURL = documentsURL.appendingPathComponent("Downloads") - - if !FileManager.default.fileExists(atPath: downloadsURL.path) { - do { - try FileManager.default.createDirectory(at: downloadsURL, withIntermediateDirectories: true, attributes: nil) - } catch { - completionHandlers[originalURL]?(.failure(error)) - return - } - } - - let fileURL = downloadsURL.appendingPathComponent("\(MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId ?? "").mp4") - do { - try FileManager.default.moveItem(at: location, to: fileURL) - completionHandlers[originalURL]?(.success(fileURL)) - progressStorage[originalURL] = nil // 清除已完成任务的进度记录 - } catch { - completionHandlers[originalURL]?(.failure(error)) - } - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { - guard let originalURL = downloadTask.originalRequest?.url else { return } - let progress = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite) - progressHandlers[originalURL]?(progress) - progressStorage[originalURL] = progress // 存储当前进度 - - } - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - guard let originalURL = task.originalRequest?.url else { return } - if let error = error { - completionHandlers[originalURL]?(.failure(error)) + func cancelAllTasksIfNeeded() { + // 根据需求,取消所有任务,或者根据任务状态进行过滤 + for key in progressStorage.keys { + session.cancel(key) } } } + //class DownloadManager { -// +// // static let shared = DownloadManager() -// +// // private init() {} -// +// // func downloadVideo(from url: URL, videoId: String, progressView: CircularProgressView, completion: @escaping (Result) -> Void) { // let destination: DownloadRequest.Destination = { _, _ in // let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] // let downloadsURL = documentsURL.appendingPathComponent("Downloads") -// +// // // 检查并创建 Downloads 文件夹 // if !FileManager.default.fileExists(atPath: downloadsURL.path) { // do { @@ -99,7 +106,7 @@ class DownloadManager: NSObject, URLSessionDownloadDelegate { // return (downloadsURL, [.removePreviousFile, .createIntermediateDirectories]) // } // } -// +// // let fileURL = downloadsURL.appendingPathComponent("\(videoId).mp4") // return (fileURL, [.removePreviousFile, .createIntermediateDirectories]) // } diff --git a/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift b/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift index 2108886..db26064 100644 --- a/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift +++ b/MusicPlayer/MP/Common/Tool(工具封装)/MP_NetWorkManager.swift @@ -64,6 +64,8 @@ class MP_NetWorkManager: NSObject { //MARK: - 固定参数 //访问数据(首次首页预览时获得) private var visitorData:String? + //固定时间点(同一天的上一周) + private lazy var currTimeDate:String = (Date().timeZone() - 7.days).toString(.custom("YYYYMMdd")) ///地址 private lazy var locaton:String = MP_LocationManager.shared.requestLocation() //预览下一阶段参数(网络请求获取) @@ -88,7 +90,7 @@ class MP_NetWorkManager: NSObject { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -105,9 +107,11 @@ class MP_NetWorkManager: NSObject { } } } - //MARK: - 异步队列 - //串行队列-预览 + //MARK: - GCD队列 + ///串行队列-预览 private var browseQueque:DispatchQueue? + ///并发队列-单曲资源预加载 + var playerItemLoadingGroup:DispatchGroup = DispatchGroup() //MARK: - 闭包 ///预览闭包(传递一个预览模块数据和完成状态) var browseRequestStateBlock:BrowseRequestStateBlock? @@ -200,7 +204,7 @@ extension MP_NetWorkManager { //web端 "clientName": "WEB_REMIX", //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -209,7 +213,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostHomeBrowse(url, parameters: parameters) } @@ -263,7 +267,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -272,7 +276,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} +// //guard netWorkStatu != .notReachable else {return} requestPostAlbumOrList(url, parameters: parameters) { results in comletion(results) } @@ -320,7 +324,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -329,7 +333,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostArtist(url, parameters: parameters) { result in comletion(result) } @@ -376,7 +380,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -385,7 +389,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostArtistMore(url, parameters: parameters) { result in comletion(result) } @@ -429,7 +433,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -438,7 +442,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostArtistMoreContinuation(url, parameters: parameters) { result in comletion(result) } @@ -483,7 +487,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -492,7 +496,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostNextList(url, parameters: parameters) { listSongs in //成功拿到列表所有歌曲(内容尚不完善) completion(listSongs) @@ -534,7 +538,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -543,7 +547,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostNextLyricsAndRelated(url, parameters: parameters) { result in completion(result) } @@ -568,7 +572,7 @@ extension MP_NetWorkManager { //MARK: - 请求player播放资源 /// 请求Player(单曲/视频)播放资源 /// - Parameter item: 请求的预览实体 - func requestPlayer(_ item: MPPositive_SongItemModel, completion:@escaping ((([String],[String]), [String]?) -> Void)){ + func requestPlayer(_ videoId: String, playlistId: String, completion:@escaping ((([String],[Float],[String]), [String]?) -> Void)){ //拼接出player路径 let path = header+point+player //设置url @@ -578,13 +582,27 @@ extension MP_NetWorkManager { } //设置参数,videoId与params参数是必定携带内容 let parameters:[String:Any] = [ - "videoId":(item.videoId ?? ""), + "videoId":videoId, "prettyPrint":"false", +// "playlistId":"OLAK5uy_knZiqQOlTDeQ3jecXrW_VIAZKdMnkLGgw", "context":[ "client":[ - //当前访问版本(日期值) +// //当前访问版本(日期值) +// "clientName": "WEB_REMIX", +// "clientVersion": "1.\(currTimeDate).01.00" + //web端 "clientName": "WEB_REMIX", - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00" + "visitorData":visitorData, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36,gzip(gfe)", +// "remoteHost":"2401:b60:13:4335:bea3:7c0b:5c4e:db84", + "originalUrl":"https://music.youtube.com/watch?v=\(videoId)", + //当前访问版本(日期值) + "clientVersion": "1.\(currTimeDate).01.00", + "platform":"MOBILE", + //语言 + "hl":Language_first_local, + //地址 + "gl":locaton ] ], "playbackContext": [ @@ -593,13 +611,13 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostPlayer(url, parameters: parameters){ resourceUlrs, coverUrls in completion(resourceUlrs, coverUrls) } } //请求单曲/视频 - private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[String]), [String]?) -> Void)) { + private func requestPostPlayer(_ url:URL, parameters:Parameters, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)) { //发送post请求 MPSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseDecodable(of: JsonPlayer.self) { [weak self] (response) in guard let self = self else {return} @@ -636,7 +654,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -645,7 +663,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostLyric(url, parameters: parameters) { lyrics in completion(lyrics) } @@ -683,7 +701,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -692,7 +710,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostRecommend(url, parameters: parameters) { results in completion(results) } @@ -736,7 +754,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -745,7 +763,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostSearchSuggestions(url, parameters: parameters) { result in completion(result) } @@ -787,7 +805,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -796,7 +814,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostSearchPreviewResults(url, parameters: parameters) { result in completion(result) } @@ -844,7 +862,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -853,7 +871,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostSearchTypeResults(url, parameters: parameters) { result in completion(result) } @@ -901,7 +919,7 @@ extension MP_NetWorkManager { "clientName": "WEB_REMIX", "visitorData":visitorData, //当前访问版本(日期值) - "clientVersion": "1.\(Date().timeZone().toString(.custom("YYYYMMdd"))).01.00", + "clientVersion": "1.\(currTimeDate).01.00", "platform":"DESKTOP", //语言 "hl":Language_first_local, @@ -910,7 +928,7 @@ extension MP_NetWorkManager { ] ] ] - guard netWorkStatu != .notReachable else {return} + //guard netWorkStatu != .notReachable else {return} requestPostSearchTypeContinuation(url, parameters: parameters) { result in completion(result) } @@ -1203,15 +1221,15 @@ extension MP_NetWorkManager { /// - Parameters: /// - player: player库 /// - completion: 传递两个字符串数组,第一个资源路径组,第二个是封面路径组 - private func parsingPlayer(_ player:JsonPlayer, completion:@escaping((([String],[String]), [String]?) -> Void)){ + private func parsingPlayer(_ player:JsonPlayer, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)){ var infos:[String]? //解析player,获取资源库和信息库 if let videoDetails = player.videoDetails { infos = parsingPlayerVideoDetails(videoDetails) } if let streamingData = player.streamingData { - parsingPlayerStreamingData(streamingData){ audios,videos in - completion((audios,videos),infos) + parsingPlayerStreamingData(streamingData){ videos,floats,approxDurationMs in + completion((videos,floats,approxDurationMs),infos) } } } @@ -1221,10 +1239,11 @@ extension MP_NetWorkManager { /// - Parameters: /// - streamingData: 资源库 /// - completion: 第一位是音频资源,第二位是视频资源 - private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String],[String]) -> Void)) { + private func parsingPlayerStreamingData(_ streamingData:JsonPlayer.StreamingData, completion:@escaping(([String],[Float],[String]) -> Void)) { var group:DispatchGroup? = DispatchGroup() var videos:[String] = [] - var audios:[String] = [] + var floats:[Float] = [] + var approxDurationMs:[String] = [] let allFormats = (streamingData.formats ?? []) + (streamingData.adaptiveFormats ?? []) for format in allFormats { if let signatureCipher = format.signatureCipher { @@ -1232,21 +1251,17 @@ extension MP_NetWorkManager { group?.enter() //获得资源签名,开始解密签名内容 parsingPlayerSignatureCipher(signatureCipher) { result in - //对数据进行拆分,分为视频资源和音频资源 - if format.mimeType?.contains("video") == true { - //这是条视频资源 - videos.append(result) - } - if format.mimeType?.contains("audio") == true { - audios.append(result) - } + //这是条视频资源 + videos.append(result) + floats.append(format.bitrate ?? 0) + approxDurationMs.append(format.approxDurationMs ?? "") // 离开DispatchGroup,表示异步任务完成 group?.leave() } } } group?.notify(queue: .main) { - completion(audios,videos) + completion(videos, floats, approxDurationMs) group = nil } } @@ -1270,11 +1285,11 @@ extension MP_NetWorkManager { //提取URl路径 let urlSubstring = originalURLString[urlStartIndex...] // 从 &url= 之后开始提取 let signString = String(originalURLString[sRange.upperBound.. Void ///播放器调整进度时执行事件 typealias MP_PlayTimerEditEndAction = () -> Void ///播放器缓存值执行事件 -typealias MP_PlayCacheValueAction = (_ currentValue:TimeInterval, _ duration:TimeInterval) -> Void +typealias MP_PlayCacheValueAction = (Float) -> Void ///播放器 class MP_PlayerManager:NSObject{ ///控制器单例 static let shared = MP_PlayerManager() + ///播放器 - private var player:AVPlayer = AVPlayer() +// private var player:AVPlayer = AVPlayer() + ///当前播放流 + private var player:FSAudioStream! +// ///预加载下一首流 + private var next:FSAudioStream! + ///计时器 + private var timer:DispatchSourceTimer! ///load模块 var loadPlayer:MPPositive_PlayerLoadViewModel!{ didSet{ @@ -68,6 +76,7 @@ class MP_PlayerManager:NSObject{ didSet{ //当播放器状态发生变化时,对播放器按钮状态进行切换 NotificationCenter.notificationKey.post(notificationName: .switch_player_status, object: playState) + } } ///获取播放器播放状态 @@ -95,22 +104,73 @@ class MP_PlayerManager:NSObject{ } ///播放器启动时执行事件记录 private var startActionBlock:MP_PlayTimerStartAction! + ///播放器运行时执行事件记录 var runActionBlock:MP_PlayTimerRunAction! ///播放器缓存值闭包 var cacheValueBlock:MP_PlayCacheValueAction! private override init() { super.init() - // 添加观察者,监听播放结束事件 - NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem) +// player.automaticallyWaitsToMinimizeStalling = false +//// player.delegate = self +// // 添加观察者,监听播放结束事件 +// NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem) //监听用户切换了音乐 NotificationCenter.notificationKey.add(observer: self, selector: #selector(userSwitchCurrentVideoAction(_ :)), notificationName: .positive_player_reload) //监听网络状态恢复可用 - NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkReachableAction(_ :)), notificationName: .net_switch_reachable) +// NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkReachableAction(_ :)), notificationName: .net_switch_reachable) + + + //创建倒计时器队列 + let queue = DispatchQueue(label: "com.MPPlayerTimer.queue") + //创建倒计时器 + timer = DispatchSource.makeTimerSource(queue: queue) + //设置计时器的起始时间以及触发事件频率为一秒一次 + timer!.schedule(deadline: .now(), repeating: .seconds(1)) + //计时器设置触发事件 + timer.setEventHandler { + [weak self] in + guard let self = self else {return} + timerAction() + } + //计时器启动 + timer.resume() } deinit { NotificationCenter.default.removeObserver(self) + player = nil + timer.cancel() + timer = nil } + ///计时器每秒执行事件 + private func timerAction() { + //判断当前播放器是否存在 + guard let player = player, playState == .Playing else {return} + DispatchQueue.main.async {[weak self] in + guard let self = self, findTurePlayer(player) else {return} + //存在,每秒获取播放进度(当前时间,总时间) + let duration = player.duration.playbackTimeInSeconds + let currentTime = player.currentTimePlayed.playbackTimeInSeconds + //调用进度闭包 + if self.runActionBlock != nil { + self.runActionBlock!(TimeInterval(currentTime), TimeInterval(duration)) + } + //检索媒体是否存入缓存 + if player.cached == true || loadPlayer.currentVideo.isDlownd == true { + if self.cacheValueBlock != nil { + self.cacheValueBlock!(1) + } + }else { + //伪装每秒缓存进度 + let byte = currentTime + 100 + let rate = Float(byte)/duration + if self.cacheValueBlock != nil { + self.cacheValueBlock!(rate) + } + } + } + } + /// 开始播放音乐 /// - Parameters: /// - startAction: 开始播放时需要执行的事件 @@ -122,143 +182,102 @@ class MP_PlayerManager:NSObject{ print("Player No Data") return } - //检索播放器状态 - switch playState { - case .Null://未启动 - break - case .Playing://启动中 - player.pause() - case .Pause://暂停中 - break - } + //清除旧流媒体 + stopAndReleaseStream(&player) //记录事件 if startAction != nil { startActionBlock = startAction } - //覆盖播放器原有的playerItem - player.replaceCurrentItem(with: loadPlayer.currentVideo.resourcePlayerItem) - //将进度回归为0 - player.seek(to: .zero) - //设置一个秒为刻度的时间值 - let interval:CMTime = .init(seconds: 1, preferredTimescale: .init(1)) - //为播放器添加运行时主线程每秒触发事件 - player.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] (time) in - guard let self = self else { return } - //转化为当前播放进度秒值 - let currentDuration = CMTimeGetSeconds(time) - //获取当前播放音乐资源的最大时间值 - let maxDuration = getMusicDuration() - if maxDuration.isNaN == false { - //判断当值进度是否超越最大时间值 - if currentDuration <= maxDuration { - //没有,执行运行时时间 - if runActionBlock != nil { - runActionBlock!(currentDuration, maxDuration) + if next != nil, (next.url == (loadPlayer.currentVideo.resourcePlayerURL! as NSURL)) { + player = next + }else { + //配置当前播放音乐 + player = .init(url: loadPlayer.currentVideo.resourcePlayerURL!) + player.maxRetryCount = 3 + } + //预加载下一首(假如有的话) + let index = loadPlayer.listViewVideos.firstIndex(of: loadPlayer.currentVideo) ?? 0 + if (loadPlayer.listViewVideos.count-1) > index { + stopAndReleaseStream(&next) + //纯在下一首,获取下一位的URL + let nextURL = loadPlayer.listViewVideos[index + 1].resourcePlayerURL + next = preloadNext(nextURL!) + } + //开始播放 + player.play() + //获取播放器状态 + player.onStateChange = { + [weak self] status in + guard let self = self, findTurePlayer(player) else {return} + switch status { + case .fsAudioStreamFailed://加载失败 + print("\(loadPlayer.currentVideo?.title ?? "")加载失败") + case .fsAudioStreamRetryingFailed://重试都失败了 + print("\(loadPlayer.currentVideo?.title ?? "")重试失败") + print("失败URL:\(String(describing: loadPlayer.currentVideo?.resourcePlayerURL))") + //重新获取资源 + loadPlayer.remakeImproveData { [weak self] in + guard let self = self else {return} + //配置当前播放音乐 + player?.url = loadPlayer.currentVideo.resourcePlayerURL! as NSURL + } + case .fsAudioStreamPlaying://加载成功 + //开始播放/正在播放 + print("\(loadPlayer.currentVideo?.title ?? "")开始播放") + if playState != .Playing { + playState = .Playing + if startAction != nil { + startAction!() } } + case .fsAudioStreamPlaybackCompleted://播放完成 + playerDidFinishPlaying() + case .fsAudioStreamEndOfFile://加载完成 + print("\(loadPlayer.currentVideo?.title ?? "")加载完毕") + default: + break } - }) - //对当前播放PlayerItem设置监听状态 - //准备状态 - loadPlayer.currentVideo?.resourcePlayerItem?.addObserver(self, forKeyPath: "status", options: [.old,.new], context: nil) - //当前缓冲值 - loadPlayer.currentVideo?.resourcePlayerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.old,.new], context: nil) - //是否具备足够播放的能力 - loadPlayer.currentVideo?.resourcePlayerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.old,.new], context: nil) - //启动全部预加载 - loadPlayer.listViewVideos.forEach({$0.preloadAsset($0.resourceAsset)}) + } + } - ///网络状态恢复正常 - @objc private func netWorkReachableAction(_ sender:Notification) { - //监听到网络状态恢复,检索当前播放器是否正在播放 - if loadPlayer?.currentVideo != nil { - //有音乐播放,获取当前播放进度 - let currentTime = loadPlayer.currentVideo!.resourcePlayerItem.currentTime() - //手动调整播放时间点,以此重启播放器缓存 - player.seek(to: currentTime) - player.play() - playState = .Playing + ///对流的检查,判断当前调用流是否是播放流 + private func findTurePlayer(_ stream:FSAudioStream) -> Bool { + guard let currentVideoURL = loadPlayer?.currentVideo?.resourcePlayerURL as? NSURL else { + return false + } + let streamURL = stream.url + if streamURL == currentVideoURL { + return true + }else { + return false } } - //实现KVO监听 - override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - guard let keyPath = keyPath else { - return - } - //根据keyPath检索 - switch keyPath { - case "status"://playerItem状态 - if let statuValue = change?[.newKey] as? Int, statuValue == AVPlayerItem.Status.readyToPlay.rawValue { - //判断当前播放器是否在播放当前音乐中 - if playState != .Playing { - //当statuVlaue值等于playerItem准备播放的值,说明已经准备好播放 - print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 已经准备好播放") - } - }else { - print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "")") - //资源更新,重新配置一下相关内容 - loadPlayer.remakeImproveData { - [weak self] in - guard let self = self else {return} - //重新播放 - play() - } - } - case "loadedTimeRanges"://当前缓冲进度 - //获取当前播放Item的缓冲值组 - if let timeRanges = loadPlayer.currentVideo?.resourcePlayerItem.loadedTimeRanges.map({$0.timeRangeValue}), let first = timeRanges.first { - //获取开始时间的秒数 - let startSeconds = first.start.seconds - //获取缓冲区的持续时间 - let durationSeconds = first.duration.seconds - //计算当前缓冲总时间 - let bufferedSeconds = startSeconds + durationSeconds - //获取当前播放音乐资源的最大时间值 - let maxDuration = getMusicDuration() - //传递缓存值 - if cacheValueBlock != nil { - cacheValueBlock!(bufferedSeconds, maxDuration) - } - } - - case "playbackLikelyToKeepUp"://是否存在足够的数据开始播放 - if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true { - if playState != .Playing { - //还未播放当前音乐,启动播放 - player.play() - playState = .Playing - //执行开始播放闭包 - if startActionBlock != nil { - startActionBlock!() - } - } - }else { - //没有足够的数据支持播放 - player.pause() - playState = .Null - } - default: - break - } - } - //MARK: - 获取当前音乐总长度 - ///获取音乐资源总时长 - private func getMusicDuration() -> TimeInterval { - return CMTimeGetSeconds(player.currentItem?.duration ?? .zero) + ///预加载下一首流 + private func preloadNext(_ url:URL) -> FSAudioStream{ + let stream = FSAudioStream(url: url) + stream?.maxRetryCount = 1 + // 开始预加载数据 + stream!.preload() + print("下一首已经在预加载") + return stream! } + + //MARK: - 音乐播放结束 //当前音乐播放结束时 - @objc private func playerDidFinishPlaying(_ sender:Notification) { + @objc private func playerDidFinishPlaying() { //检索播放器对象 guard playState == .Playing else { return } switch playType { case .single: + var postion = FSStreamPosition() + postion.position = 0 //重播 - player.seek(to: CMTime.zero) + player.seek(to: postion) player.play() default: //当前音乐播放器正在播放中,下一首 @@ -310,7 +329,8 @@ class MP_PlayerManager:NSObject{ resumeAction!() } //继续播放器 - player.play() +// player.play() + player.pause() //切换播放器状态 playState = .Playing } @@ -323,7 +343,8 @@ class MP_PlayerManager:NSObject{ return } //继续播放器 - player.play() +// player.play() + player.pause() //切换播放器状态 playState = .Playing } @@ -337,7 +358,7 @@ class MP_PlayerManager:NSObject{ print("Player is not started") return } - player.pause() + player.stop() playState = .Null } //MARK: - 切歌(上一首/下一首) @@ -430,21 +451,21 @@ class MP_PlayerManager:NSObject{ @objc private func userSwitchCurrentVideoAction(_ sender:Notification) { //将播放器状态调整未播放 playState = .Null - //暂停播放 - player.pause() - //优先获取传递的值 - if let video = sender.object as? MPPositive_SongViewModel { - //切歌时移除KVO监听 - video.resourcePlayerItem.removeObserver(self, forKeyPath: "status") - video.resourcePlayerItem.removeObserver(self, forKeyPath: "loadedTimeRanges") - video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") + //清理所有的流 + if player != nil { + //清除所有流 + stopAndReleaseStream(&player) } if cacheValueBlock != nil { - cacheValueBlock!(0, 1) + cacheValueBlock!(0) } - if loadPlayer.currentVideo != nil { - //开始播放 - play(startAction: startActionBlock) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + [weak self] in + guard let self = self else {return} + if loadPlayer.currentVideo != nil { + //开始播放 + play(startAction: startActionBlock) + } } } @@ -460,22 +481,85 @@ class MP_PlayerManager:NSObject{ /// - Parameters: /// - progress: 要调整进度值(保证在0-1范围内,超出该方法不会响应) func setEditProgressEnd(_ progress:Float, endAction:MP_PlayTimerEditEndAction? = nil) { - guard playState != .Null else { + guard playState != .Null, let player = player, findTurePlayer(player) else { return } guard progress >= 0, progress <= 1 else { return } - //根据当前进度值设置时间节点 - let timePoint:Double = Double(progress)*getMusicDuration() //设置对应的时间值 - let time:CMTime = .init(seconds: timePoint, preferredTimescale: CMTimeScale(NSEC_PER_SEC)) - //调整播放器时间 - player.seek(to: time) - //恢复播放 - resume() - if endAction != nil { - endAction!() + var time:FSStreamPosition = .init() + time.position = progress + //获取当前值的大小 + let currentTime = player.currentTimePlayed.playbackTimeInSeconds + if progress != currentTime { + //调整播放器时间 + player.seek(to: time) + //恢复播放 + resume() + if endAction != nil { + endAction!() + } } } + ///清除流的方法 + private func stopAndReleaseStream(_ stream: inout FSAudioStream?) { + stream?.stop() // 停止流 + stream?.onStateChange = nil // 清除所有可能的回调 + stream?.onFailure = nil + stream = nil + } } +////MARK: - 媒体项目协议处理 +//extension MP_PlayerManager: MP_AVPlayerItemDelegate { +// ///当媒体项目初次缓存后 +// func playerItemReadyToPlay(_ playerItem: MP_AVPlayerItem) { +// DispatchQueue.main.async { +// [weak self] in +// guard let self = self else {return} +// if playState != .Playing { +// //还未播放当前音乐,启动播放 +// player.play() +// playState = .Playing +// //执行开始播放闭包 +// if startActionBlock != nil { +// startActionBlock!() +// } +// +// } +// } +// } +// ///当媒体项目收到新缓存后 +// func playerItem(_ playerItem: MP_AVPlayerItem, progress:Float) { +// DispatchQueue.main.async { +// [weak self] in +// guard let self = self else {return} +// //传递缓存值 +// if cacheValueBlock != nil { +// cacheValueBlock!(progress) +// } +// } +// } +// ///当媒体项目完全加载后 +// func playerItem(_ playerItem: MP_AVPlayerItem, didFinishLoadingData data: Data) { +// print("\(loadPlayer.currentVideo.title ?? "") 已经完全缓存完毕") +// DispatchQueue.main.async { +// [weak self] in +// guard let self = self else {return} +// //传递缓存值 +// if cacheValueBlock != nil { +// cacheValueBlock!(1) +// } +// } +// } +// ///当媒体项目加载数据中断(比如断网了),导致停止播放时 +// func playerItemPlaybackStalled(_ playerItem: MP_AVPlayerItem) { +// print("中断了") +// } +// ///当媒体项目加载错误时调用。 +// func playerItem(_ playerItem: MP_AVPlayerItem, loadingError error: any Error) { +// print("\(loadPlayer.currentVideo.title ?? "") 发生错误,---\(error)") +// } +// +// +//} diff --git a/MusicPlayer/MP/MPPositive/Models/JsonStructs(js文件结构)/MPPositive_JsonPlayer.swift b/MusicPlayer/MP/MPPositive/Models/JsonStructs(js文件结构)/MPPositive_JsonPlayer.swift index a404fe3..eada762 100644 --- a/MusicPlayer/MP/MPPositive/Models/JsonStructs(js文件结构)/MPPositive_JsonPlayer.swift +++ b/MusicPlayer/MP/MPPositive/Models/JsonStructs(js文件结构)/MPPositive_JsonPlayer.swift @@ -46,23 +46,31 @@ struct JsonPlayer: Codable { struct Format: Codable { ///格式标签 let itag: Int? + ///比特率 + let bitrate:Float? ///格式编码 let mimeType:String? ///格式名 let qualityLabel:String? ///资源地址(双加密) let signatureCipher:String? + ///总长度 + let approxDurationMs:String? enum CodingKeys: String, CodingKey { case itag = "itag" + case bitrate = "bitrate" case mimeType = "mimeType" case qualityLabel = "qualityLabel" case signatureCipher = "signatureCipher" + case approxDurationMs = "approxDurationMs" } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) itag = try values.decodeIfPresent(Int.self, forKey: .itag) + bitrate = try values.decodeIfPresent(Float.self, forKey: .bitrate) mimeType = try values.decodeIfPresent(String.self, forKey: .mimeType) qualityLabel = try values.decodeIfPresent(String.self, forKey: .qualityLabel) + approxDurationMs = try values.decodeIfPresent(String.self, forKey: .approxDurationMs) signatureCipher = try values.decodeIfPresent(String.self, forKey: .signatureCipher) } } diff --git a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionListModel.swift b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionListModel.swift index affd95a..421d761 100644 --- a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionListModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionListModel.swift @@ -13,8 +13,10 @@ class MPPositive_CollectionListModel: NSManagedObject, MP_CoreDataManageableDele typealias ManagedObject = MPPositive_CollectionListModel ///封面 @NSManaged var coverImage:URL! - ///标题(单曲标题) + ///标题 @NSManaged var title:String? + ///副标题 + @NSManaged var subtitle:String? ///列表专辑预览ID @NSManaged var browseId:String? ///列表专辑预览参数 diff --git a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionSongModel.swift b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionSongModel.swift index 7e66f35..d9823fd 100644 --- a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionSongModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_CollectionSongModel.swift @@ -19,6 +19,8 @@ class MPPositive_CollectionSongModel: NSManagedObject, MP_CoreDataManageableDele @NSManaged var subtitle:String? ///播放的VideoID @NSManaged var videoId:String? - - + ///歌词ID + @NSManaged var lyricsID:String? + ///相关内容ID + @NSManaged var relatedID:String? } diff --git a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_DownloadItemModel.swift b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_DownloadItemModel.swift index 1a70877..b223e8d 100644 --- a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_DownloadItemModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_DownloadItemModel.swift @@ -11,12 +11,10 @@ import CoreData @objc(MPPositive_DownloadItemModel) class MPPositive_DownloadItemModel: NSManagedObject, MP_CoreDataManageableDelegate, MP_CoreDataOperationDelegate { typealias ManagedObject = MPPositive_DownloadItemModel - ///资源存储地址(沙盒路径后缀名) - @NSManaged var resourcePath:String! ///封面 - @NSManaged var coverImage:URL! + @NSManaged var coverImage:String! ///预览图片 - @NSManaged var reviewImage:URL! + @NSManaged var reviewImage:String! ///标题(单曲标题) @NSManaged var title:String? ///长文本标题(作者/播放次数/点赞次数) @@ -24,9 +22,11 @@ class MPPositive_DownloadItemModel: NSManagedObject, MP_CoreDataManageableDelega ///单曲长度文本(歌曲长度) @NSManaged var lengthText:String? ///署名文本(歌手) - @NSManaged var shortBylineText:String? + @NSManaged var shortBylineText:String? ///歌词 - @NSManaged var lyrics:String? + @NSManaged var lyrics:String? + ///歌词ID + @NSManaged var lyricsID:String? ///播放的VideoID @NSManaged var videoId:String! ///相关内容ID diff --git a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SearchTagModel.swift b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SearchTagModel.swift new file mode 100644 index 0000000..87c4e3d --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SearchTagModel.swift @@ -0,0 +1,18 @@ +// +// MPPositive_SearchTagModel.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit +import CoreData +///搜索标签/历史记录模型 +@objc(MPPositive_SearchTagModel) +class MPPositive_SearchTagModel: NSManagedObject, MP_CoreDataManageableDelegate, MP_CoreDataOperationDelegate { + typealias ManagedObject = MPPositive_SearchTagModel + ///添加日期 + @NSManaged var date:Date! + ///添加文本 + @NSManaged var text:String! +} diff --git a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SongItemModel.swift b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SongItemModel.swift index 2fbebb7..e4d794e 100644 --- a/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SongItemModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/Models/MPPositive_SongItemModel.swift @@ -10,11 +10,9 @@ import UIKit class MPPositive_SongItemModel: NSObject { ///序列号(在当前列表中的排序) var index:Int! - ///视频源路径(等级制,默认取第一条最低质量) - var resourceUrls:[String]? - ///音频资源路径(等级制,默认取第一条最低质量) - var audioUrls:[String]? - ///封面路径(默认拿最后一条最清晰) + ///视频源路径组(等级制,默认取第一条最低质量) + var resourceUrls:[String]? + ///封面路径组(默认拿最后一条最清晰) var coverUrls:[String]? ///预览图片(默认拿最后一条最清晰) var reviewUrls:[String]? @@ -28,9 +26,16 @@ class MPPositive_SongItemModel: NSObject { var shortBylineText:String? ///歌词ID var lyricsID:String? + ///歌词 + var lyrics:String? ///播放的VideoID var videoId:String! ///相关内容ID var relatedID:String! - + ///比特率 + var bitrates:[Float]? + ///总长度 + var approxDurationMs:[String]? + ///列表ID + var playlistId:String? } diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionArtistViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionArtistViewModel.swift new file mode 100644 index 0000000..4f8734e --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionArtistViewModel.swift @@ -0,0 +1,40 @@ +// +// MPPositive_CollectionArtistViewModel.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit +import Kingfisher +///收藏艺术家实体 +class MPPositive_CollectionArtistViewModel: NSObject { + ///封面 + var coverURL:URL! + ///标题 + var title:String? + ///副标题 + var subtitle:String? + ///收藏列表实体 + var collectionArtist:MPPositive_CollectionArtistModel + + init(_ artist:MPPositive_CollectionArtistModel) { + collectionArtist = artist + super.init() + configure() + } + //配置数据 + private func configure() { + if collectionArtist.coverImage != nil { + coverURL = collectionArtist.coverImage + } + title = collectionArtist.title + subtitle = collectionArtist.subtitle + } + //设置预览图 + func setImage(_ imageView:UIImageView) { + if coverURL != nil { + imageView.kf.setImage(with: coverURL, placeholder: placeholderImage) + } + } +} diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionListViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionListViewModel.swift new file mode 100644 index 0000000..d3db7d9 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionListViewModel.swift @@ -0,0 +1,40 @@ +// +// MPPositive_CollectionListViewModel.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit +import Kingfisher +///收藏歌单实体ViewModel +class MPPositive_CollectionListViewModel: NSObject { + ///封面 + var coverURL:URL! + ///标题 + var title:String? + ///副标题 + var subtitle:String? + ///收藏列表实体 + var collectionList:MPPositive_CollectionListModel + + init(_ list:MPPositive_CollectionListModel) { + collectionList = list + super.init() + configure() + } + //配置数据 + private func configure() { + if collectionList.coverImage != nil { + coverURL = collectionList.coverImage + } + title = collectionList.title + subtitle = collectionList.subtitle + } + //设置预览图 + func setImage(_ imageView:UIImageView) { + if coverURL != nil { + imageView.kf.setImage(with: coverURL, placeholder: placeholderImage) + } + } +} diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionSongViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionSongViewModel.swift new file mode 100644 index 0000000..c668198 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_CollectionSongViewModel.swift @@ -0,0 +1,40 @@ +// +// MPPositive_CollectionSongViewModel.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit +import Kingfisher +///收藏单曲实例ViewModel +class MPPositive_CollectionSongViewModel: NSObject { + ///封面 + var coverURL:URL! + ///标题 + var title:String? + ///副标题 + var subtitle:String? + ///收藏列表实体 + var collectionSong:MPPositive_CollectionSongModel + + init(_ song:MPPositive_CollectionSongModel) { + collectionSong = song + super.init() + configure() + } + //配置数据 + private func configure() { + if collectionSong.coverImage != nil { + coverURL = collectionSong.coverImage + } + title = collectionSong.title + subtitle = collectionSong.subtitle + } + //设置预览图 + func setImage(_ imageView:UIImageView) { + if coverURL != nil { + imageView.kf.setImage(with: coverURL, placeholder: placeholderImage) + } + } +} diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_DownloadViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_DownloadViewModel.swift new file mode 100644 index 0000000..762e796 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_DownloadViewModel.swift @@ -0,0 +1,39 @@ +// +// MPPositive_DownloadViewModel.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit +import Kingfisher +///下载实例ViewModel +class MPPositive_DownloadViewModel: NSObject { + ///预览图片 + var reviewURL:URL? + ///标题 + var title:String? + ///副标题 + var subtitle:String? + ///下载单曲实体 + var loadItem:MPPositive_DownloadItemModel + init(_ loadItem:MPPositive_DownloadItemModel) { + self.loadItem = loadItem + super.init() + configure() + } + //配置数据 + private func configure() { + if let url = URL(string: loadItem.reviewImage) { + reviewURL = url + } + title = loadItem.title + subtitle = loadItem.shortBylineText + } + //设置预览图 + func setImage(_ imageView:UIImageView) { + if reviewURL != nil { + imageView.kf.setImage(with: reviewURL, placeholder: placeholderImage) + } + } +} diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift index ec6b6a5..2d4193f 100644 --- a/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/ListViewModels/MPPositive_SongViewModel.swift @@ -8,13 +8,20 @@ import UIKit import AVKit import AVFoundation +import FreeStreamer class MPPositive_SongViewModel: NSObject { ///排序号 var index:Int! ///音乐资源路径 - var resourcePlayerItem:AVPlayerItem! +// var resourcePlayerItem:AVPlayerItem! +// var resourcePlayerItem:MP_AVPlayerItem! +// var resourcePlayerItem:CachingPlayerItem! + ///播放实例 +// var resourcePlayerItem:FSAudioStream? + ///播放路径 + var resourcePlayerURL:URL? ///资源加载路径 - var resourceAsset:AVURLAsset! +// var resourceAsset:MP_AVURLAsset! ///封面 var coverUrl:URL? ///标题 @@ -31,31 +38,28 @@ class MPPositive_SongViewModel: NSObject { var isDlownd:Bool? ///音乐实体 var song:MPPositive_SongItemModel! - ///是否进行过预加载 - var isPloading:Bool = false - // 标记为已取消 - private var isCancelled = false init(_ song:MPPositive_SongItemModel) { super.init() self.song = song +// resourcePlayerItem = nil configure() } deinit { - //释放内存 - resourcePlayerItem = nil - resourceAsset = nil - isCancelled = true - print("\(title ?? "")被释放了") + resourcePlayerURL = nil } //数据配置 - private func configure() { + func configure() { + reloadCollectionAndDownLoad() index = song.index - //资源路径默认取第一条 - if song.resourceUrls?.first != nil, let first = URL(string: song.resourceUrls?.first ?? ""){ - //创建一个资产链接并允许它预加载 - resourceAsset = .init(url: first, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true]) - //创建PlayerItem - resourcePlayerItem = .init(asset: resourceAsset) + + if let first = song.resourceUrls?.first { + //判断是否下载 + if isDlownd == true { + resourcePlayerURL = .init(string:first) + }else { + //没有完成下载,使用网络路径 + resourcePlayerURL = .init(string: first) + } } //封面路径默认取最后一条 if song.reviewUrls?.first != nil { @@ -70,21 +74,21 @@ class MPPositive_SongViewModel: NSObject { subtitle = song.shortBylineText! } //歌词 - if song.lyricsID != nil { - //执行网络请求拿到歌词数据 - MP_NetWorkManager.shared.requestLyric(song.lyricsID!) {[weak self] lyrics in - self?.lyrics = lyrics + if song.lyrics != nil { + lyrics = song.lyrics + }else { + if song.lyricsID != nil { + //执行网络请求拿到歌词数据 + MP_NetWorkManager.shared.requestLyric(song.lyricsID!) {[weak self] lyrics in + self?.lyrics = lyrics + self?.song.lyrics = lyrics + } } } //相关内容 if song.relatedID != nil { relatedId = song.relatedID } - reloadCollectionAndDownLoad() - //执行预加载 -// if isPloading == false { -// preloadAsset(resourceAsset) -// } } //页面状态更新 func reloadCollectionAndDownLoad() { @@ -94,66 +98,4 @@ class MPPositive_SongViewModel: NSObject { //检索是否下载 isDlownd = MPPositive_DownloadItemModel.fetch(.init(format: "videoId == %@", song.videoId)).count != 0 } - - //执行预加载 - func preloadAsset(_ asset:AVURLAsset) { - guard isPloading == false else { - return - } - self.isPloading = true - //执行预加载 - if #available(iOS 16, *) { - //ios16以上的情况 - Task{ - do{ - let playable = try await asset.load(.isPlayable) - guard !isCancelled else { - return - } - if playable == true { - print("\(self.title ?? "")预加载成功") - self.isPloading = true - }else { - //检索预加载失败原因 - switch asset.status(of: .isPlayable) { - case .failed(let erro): - print("\(title ?? "")预加载失败,失败原因:\(erro.localizedDescription)") - self.isPloading = false - default: - self.isPloading = false - } - } - }catch{ - print("预加载失败:\(error.localizedDescription)") - } - } - }else { - //ios16以下的情况 - let keys = ["playable"] - asset.loadValuesAsynchronously(forKeys: keys) { - [weak self] in - guard let self = self, !isCancelled else {return} - for key in keys { - var error: NSError? = nil - let status = asset.statusOfValue(forKey: key, error: &error) - switch status { - case .loaded: - // key成功加载,资源准备就绪 - DispatchQueue.main.async { - print("\(self.title ?? "")预加载成功") - self.isPloading = true - } - case .failed: - print("\(title ?? "")预加载失败,失败原因:\(error?.localizedDescription ?? "")") - self.isPloading = false - case .cancelled: - print("\(title ?? "")预加载被取消了") - self.isPloading = false - default: - break - } - } - } - } - } } diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_LoadCoreModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_LoadCoreModel.swift new file mode 100644 index 0000000..9047fe6 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_LoadCoreModel.swift @@ -0,0 +1,91 @@ +// +// MPPositive_LoadCoreModel.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit +///数据库单例类 +class MPPositive_LoadCoreModel: NSObject { + static let shared = MPPositive_LoadCoreModel() + ///收藏列表ViewModel组 + var listViewModels:[MPPositive_CollectionListViewModel] = [] + ///收藏艺术家ViewModel组 + var artistViewModels:[MPPositive_CollectionArtistViewModel] = [] + ///收藏歌曲ViewModel组 + var songViewModels:[MPPositive_CollectionSongViewModel] = [] + ///下载歌曲ViewModel组 + var loadViewModels:[MPPositive_DownloadViewModel] = [] + ///搜索结果 + var searchTags:[MPPositive_SearchTagModel] = [] + private override init() { + super.init() + reloadCollectionListViewModels(nil) + reloadCollectionArtistViewModels(nil) + reloadCollectionSongViewModel(nil) + reloadLoadSongViewModel(nil) + reloadSearchTags(nil) + } + + ///更新收藏列表组 + func reloadCollectionListViewModels(_ complection:(() -> Void)?) { + listViewModels = [] + let array = MPPositive_CollectionListModel.fetchAll() + array.forEach { item in + listViewModels.append(.init(item)) + } + //倒叙一下 + listViewModels = listViewModels.reversed() + if complection != nil { + complection!() + } + } + ///更新收藏艺术家组 + func reloadCollectionArtistViewModels(_ complection:(() -> Void)?) { + artistViewModels = [] + let array = MPPositive_CollectionArtistModel.fetchAll() + array.forEach { item in + artistViewModels.append(.init(item)) + } + //倒叙一下 + artistViewModels = artistViewModels.reversed() + if complection != nil { + complection!() + } + } + ///更新收藏歌曲组 + func reloadCollectionSongViewModel(_ complection:(() -> Void)?) { + songViewModels = [] + let array = MPPositive_CollectionSongModel.fetchAll() + array.forEach { item in + songViewModels.append(.init(item)) + } + //倒叙一下 + songViewModels = songViewModels.reversed() + if complection != nil { + complection!() + } + } + ///更新下载歌曲库 + func reloadLoadSongViewModel(_ complection:(() -> Void)?) { + loadViewModels = [] + let array = MPPositive_DownloadItemModel.fetchAll() + array.forEach { item in + loadViewModels.append(.init(item)) + } + //倒叙一下 + loadViewModels = loadViewModels.reversed() + if complection != nil { + complection!() + } + } + ///更新搜索历史 + func reloadSearchTags(_ complection:(() -> Void)?) { + searchTags = [] + searchTags = MPPositive_SearchTagModel.fetchAll().sorted(by: {$1.date > $0.date}) + if complection != nil { + complection!() + } + } +} diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift index d44a82c..394629c 100644 --- a/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_PlayerLoadViewModel.swift @@ -43,7 +43,7 @@ class MPPositive_PlayerLoadViewModel: NSObject { self.listViewVideos = [] } - ///将选中Video的上下2项包括本身总计3项Video进行补全转为ViewModel,并播放这首音乐 + ///将选中Video的上下1项包括本身总计3项Video进行补全转为ViewModel,并播放这首音乐 func improveData(_ targetVideoId:String, isRandom:Bool = false) { //对于选中Video的集合 var array:[MPPositive_SongItemModel] = [] @@ -82,25 +82,41 @@ class MPPositive_PlayerLoadViewModel: NSObject { let videoIDs = Set(listViewVideos.map({$0.song.videoId})) //比较videoID,去掉已经补完的内容 array = array.filter({!videoIDs.contains($0.videoId)}) + group = DispatchGroup() //去重完毕,对剩下内容补完 for item in array { group?.enter() //补全歌词id和相关内容id - improveDataforLycirsAndRelated(item) {[weak self] (result) in - item.lyricsID = result.0 - item.relatedID = result.1 - self?.group?.leave() + if item.lyricsID == nil || item.relatedID == nil { + improveDataforLycirsAndRelated(item) {[weak self] (result) in + item.lyricsID = result.0 + item.relatedID = result.1 + self?.group?.leave() + } + }else { + group?.leave() } group?.enter() - //补全资源路径组和封面路径组 - improveDataforResouceAndCover(item) {[weak self] resourceUrls, coverUrls in - item.resourceUrls = resourceUrls.1 - item.audioUrls = resourceUrls.0 - item.coverUrls = coverUrls + //判断当前videoID是否进行过下载 + if let resource = getDocumentsFileURL(item.videoId) { + //下载过,resource直接填入 + item.resourceUrls = [resource] //补全完成,转化为ViewModel,并添加进listViewVideos - self?.listViewVideos.append(.init(item)) - self?.group?.leave() + listViewVideos.append(.init(item)) + group?.leave() + }else { + //没有下载过 + //补全资源路径组和封面路径组 + improveDataforResouceAndCover(item) {[weak self] resourceUrls, coverUrls in + item.resourceUrls = resourceUrls.0 + item.bitrates = resourceUrls.1 + item.approxDurationMs = resourceUrls.2 + item.coverUrls = coverUrls + //补全完成,转化为ViewModel,并添加进listViewVideos + self?.listViewVideos.append(.init(item)) + self?.group?.leave() + } } } group?.notify(queue: .main, execute: { @@ -117,17 +133,21 @@ class MPPositive_PlayerLoadViewModel: NSObject { //当前歌曲不能播放,需要重新配置资源 improveDataforResouceAndCover(currentVideo.song) {[weak self] resourceUrls, coverUrls in guard let self = self else {return} - currentVideo.song.resourceUrls = resourceUrls.1 - currentVideo.song.audioUrls = resourceUrls.0 + currentVideo.song.resourceUrls = resourceUrls.0 + currentVideo.song.bitrates = resourceUrls.1 + currentVideo.song.approxDurationMs = resourceUrls.2 //成功更新资源,将重新补完的歌曲,放进listViewVideos中 listViewVideos.forEach({ item in if item.song.videoId == self.currentVideo.song.videoId { item.song.resourceUrls = self.currentVideo.song.resourceUrls - item.song.audioUrls = self.currentVideo.song.audioUrls + item.song.bitrates = self.currentVideo.song.bitrates + item.song.approxDurationMs = self.currentVideo.song.approxDurationMs } }) - currentVideo.resourceAsset = .init(url: .init(string: currentVideo.song.resourceUrls!.first!)!) - currentVideo.resourcePlayerItem = .init(asset: currentVideo.resourceAsset!) +// currentVideo.resourceAsset = .init(url: .init(string: currentVideo.song.resourceUrls!.first!)!) +// currentVideo.resourcePlayerItem = .init(asset: currentVideo.resourceAsset!) +// currentVideo.resourcePlayerItem = .init(url: .init(string: (currentVideo.song.resourceUrls?.first ?? ""))!, bitrate: Int64(currentVideo.song.bitrates?.first ?? 0), title: currentVideo.title, videoId: currentVideo.song.videoId) + currentVideo.configure() //当值变化时通知播放器页面,更新UI NotificationCenter.notificationKey.post(notificationName: .positive_player_reload) completion() @@ -156,6 +176,11 @@ class MPPositive_PlayerLoadViewModel: NSObject { } } } + //判断当前videoId是不是下载过 + private func findVideoIdForDocument(_ videoId:String) -> Bool { + return MPPositive_DownloadItemModel.fetch(.init(format: "videoId == %@", videoId)).count != 0 + } + ///调用next对单曲数据歌词ID与相关ID补全 private func improveDataforLycirsAndRelated(_ song:MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) { //单曲补全需要再次调用next接口 @@ -164,9 +189,9 @@ class MPPositive_PlayerLoadViewModel: NSObject { } } ///调用player对资源路径和封面路径补全 - private func improveDataforResouceAndCover(_ song:MPPositive_SongItemModel, completion:@escaping((([String],[String]), [String]?) -> Void)) { + private func improveDataforResouceAndCover(_ song:MPPositive_SongItemModel, completion:@escaping((([String],[Float],[String]), [String]?) -> Void)) { //单曲补全需要调用player接口 - MP_NetWorkManager.shared.requestPlayer(song) { resourceUrls, coverUrls in + MP_NetWorkManager.shared.requestPlayer(song.videoId, playlistId: song.playlistId ?? "") { resourceUrls, coverUrls in completion(resourceUrls,coverUrls) } } diff --git a/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_SearchResultsLoadViewModel.swift b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_SearchResultsLoadViewModel.swift index a68556a..3966c0a 100644 --- a/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_SearchResultsLoadViewModel.swift +++ b/MusicPlayer/MP/MPPositive/Models/ViewModels/LoadViewModels/MPPositive_SearchResultsLoadViewModel.swift @@ -29,6 +29,12 @@ class MPPositive_SearchResultsLoadViewModel: NSObject { } //根据用户输入文本内容请求搜索接口 private func getSearchResults(_ text:String) { + //同时新增一个搜索历史 + let tag = MPPositive_SearchTagModel.create() + tag.date = Date().timeZone() + tag.text = text + MPPositive_SearchTagModel.save() + MPPositive_LoadCoreModel.shared.reloadSearchTags(nil) MP_NetWorkManager.shared.requestSearchPreviewResults(text) { [weak self] results in self?.sectionLists = results } diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_NavigationController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_NavigationController.swift index 95df364..87eb3f3 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_NavigationController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_NavigationController.swift @@ -13,6 +13,7 @@ class MPPositive_NavigationController: MP_NavigationController { //每一次push都会执行这个方法,push之前设置viewController的hidesBottomBarWhenPushed override func pushViewController(_ viewController: UIViewController, animated: Bool) { viewController.hidesBottomBarWhenPushed = true + NotificationCenter.notificationKey.post(notificationName: .positive_nav_push) super.pushViewController(viewController, animated: true) viewController.hidesBottomBarWhenPushed = false } @@ -30,13 +31,14 @@ class MPPositive_NavigationController: MP_NavigationController { let count = self.children.count-2 let controller = self.children[count] controller.hidesBottomBarWhenPushed = true - NotificationCenter.notificationKey.post(notificationName: .sideA_hidden_show) + NotificationCenter.notificationKey.post(notificationName: .positive_nav_push) return super.popViewController(animated: true) } //如果viewController栈中存在的ViewController的个数为两个,再返回上一级界面就是根界面了 //那么要对tabbar进行显示 let controller:UIViewController = self.children[0] controller.hidesBottomBarWhenPushed = false + NotificationCenter.notificationKey.post(notificationName: .positive_nav_pop) return super.popViewController(animated: true) } } diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_TabBarController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_TabBarController.swift index 1e924e1..84ed5f0 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_TabBarController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Base(基类,导航栏,标签栏)/MPPositive_TabBarController.swift @@ -11,6 +11,14 @@ class MPPositive_TabBarController: UITabBarController, UIViewControllerTransitio //自定义tabBar private lazy var customTabBar:MPPositive_CustomTabBar = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 72*width)) private lazy var bottomView:MPPositive_BottomShowView = .init(frame: .init(x: 0, y: 0, width: 351, height: 82)) + //底部展示状态 + private var isbottomShow:Bool = false{ + willSet{ + if isbottomShow != newValue { + switch_bottomShowAnimation(newValue) + } + } + } override func viewDidLoad() { super.viewDidLoad() self.setValue(customTabBar, forKey: "tabBar") @@ -57,6 +65,8 @@ class MPPositive_TabBarController: UITabBarController, UIViewControllerTransitio NotificationCenter.notificationKey.add(observer: self, selector: #selector(pupPlayerAction), notificationName: .pup_player_vc) NotificationCenter.notificationKey.add(observer: self, selector: #selector(bottomAnimationAction(_:)), notificationName: .pup_bottom_show) NotificationCenter.notificationKey.add(observer: self, selector: #selector(bottomAnimationAction(_:)), notificationName: .player_delete_list) + NotificationCenter.notificationKey.add(observer: self, selector: #selector(pushAction(_ :)), notificationName: .positive_nav_push) + NotificationCenter.notificationKey.add(observer: self, selector: #selector(popAction(_ :)), notificationName: .positive_nav_pop) } deinit { //移除所有监听 @@ -84,7 +94,7 @@ extension MPPositive_TabBarController { let playerVC = MPPositive_PlayerViewController() playerVC.modalPresentationStyle = .fullScreen playerVC.recommendBlock = { - let recommendVC = MPPositive_RecommendViewController(MP_PlayerManager.shared.loadPlayer.currentVideo.relatedId ?? "") + let recommendVC = MPPositive_RecommendViewController(MP_PlayerManager.shared.loadPlayer.currentVideo.song.relatedID) self?.viewControllers![self?.selectedIndex ?? 0].children[0].navigationController?.pushViewController(recommendVC, animated: true) } self?.present(playerVC, animated: true) @@ -93,7 +103,7 @@ extension MPPositive_TabBarController { } //切换底部音乐模块状态 @objc private func bottomAnimationAction(_ sender:Notification) { - switch_bottomShowAnimation(MP_PlayerManager.shared.loadPlayer != nil) + isbottomShow = MP_PlayerManager.shared.loadPlayer != nil } //底部BottomView的切换动画 private func switch_bottomShowAnimation(_ state:Bool) { @@ -109,4 +119,27 @@ extension MPPositive_TabBarController { } } } + //页面push事件 + @objc private func pushAction(_ sender:Notification) { + //检索页面状态 + if isbottomShow == true { + //将bottomView,向下移动83 + UIView.animate(withDuration: 0.3) { + [weak self] in + guard let self = self else { return } + bottomView.transform = .init(translationX: 0, y: -82*width) + } + } + } + @objc private func popAction(_ sender:Notification) { + //检索页面状态 + if isbottomShow == true { + //将bottomView,向上移动83 + UIView.animate(withDuration: 0.3) { + [weak self] in + guard let self = self else { return } + bottomView.transform = .init(translationX: 0, y: -145*width) + } + } + } } diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LibraryViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LibraryViewController.swift index 7a6c832..6b4d110 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LibraryViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LibraryViewController.swift @@ -7,23 +7,219 @@ import UIKit -class MPPositive_LibraryViewController: UIViewController { - +class MPPositive_LibraryViewController: MPPositive_BaseViewController { + //顶部图片 + private lazy var topImageView:UIImageView = { + let imageView:UIImageView = .init(image: .init(named: "Center_Top_bg")) + imageView.contentMode = .scaleAspectFill + return imageView + }() + //毛玻璃效果View + private lazy var blurView:UIVisualEffectView = setBlurView() + //阴影View + private lazy var maskImageView:UIImageView = { + let imageView:UIImageView = .init(image: .init(named: "Player'mask")) + imageView.contentMode = .scaleAspectFill + return imageView + }() + //标题Label + private lazy var titleLabel:UILabel = createLabel("Your Library", font: .systemFont(ofSize: 32*width, weight: .regular), textColor: .white, textAlignment: .left) + //新增按钮 + private lazy var addBtn:UIButton = { + let btn:UIButton = UIButton() + btn.setBackgroundImage(UIImage(named: "Center_Add_'logo"), for: .normal) + btn.addTarget(self, action: #selector(addClick(_:)), for: .touchUpInside) + return btn + }() + ///收藏歌手Label + private lazy var artistsLabel:UILabel = createLabel("0", font: .systemFont(ofSize: 12*width, weight: .light), textColor: .white, textAlignment: .center) + ///收藏单曲Label + private lazy var songsLabel:UILabel = createLabel("0", font: .systemFont(ofSize: 12*width, weight: .light), textColor: .white, textAlignment: .center) + ///下载单曲Label + private lazy var loadsLabel:UILabel = createLabel("0", font: .systemFont(ofSize: 12*width, weight: .light), textColor: .white, textAlignment: .center) + ///事件View组 + private lazy var actionViews:UIView = showTopView() + ///tableView + private lazy var tableView:UITableView = { + let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain) + if #available(iOS 15.0, *) { + tableView.sectionHeaderTopPadding = 0 + } + tableView.backgroundColor = .clear + tableView.separatorStyle = .none + tableView.estimatedRowHeight = 200 + tableView.rowHeight = UITableView.automaticDimension + tableView.dataSource = self + tableView.delegate = self + tableView.register(MPPositive_LibraryTableViewCell.self, forCellReuseIdentifier: MPPositive_LibraryTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) + return tableView + }() + private let MPPositive_LibraryTableViewCellID = "MPPositive_LibraryTableViewCell" override func viewDidLoad() { super.viewDidLoad() - - // Do any additional setup after loading the view. + setTitle("") + configure() + view.backgroundColor = .init(hex: "#000000") + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + reload() + } + //刷新列表 + private func reload() { + MPPositive_LoadCoreModel.shared.reloadCollectionListViewModels { + [weak self] in + guard let self = self else {return} + tableView.showMessage(MPPositive_LoadCoreModel.shared.listViewModels.count, title: "No Lists") + artistsLabel.text = "\(MPPositive_LoadCoreModel.shared.artistViewModels.count)" + songsLabel.text = "\(MPPositive_LoadCoreModel.shared.songViewModels.count)" + loadsLabel.text = "\(MPPositive_LoadCoreModel.shared.loadViewModels.count)" + tableView.reloadData() + } + } + ///新增 + @objc private func addClick(_ sender:UIButton) { + + } + private func configure() { + view.addSubview(topImageView) + topImageView.snp.makeConstraints { make in + make.left.top.right.equalToSuperview() + make.height.equalTo(291*width) + } + view.addSubview(blurView) + blurView.snp.makeConstraints { make in + make.left.top.right.equalToSuperview() + make.height.equalTo(topImageView) + } + view.addSubview(maskImageView) + maskImageView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalTo(topImageView) + make.height.equalTo(109*width) + } + navView.addSubview(titleLabel) + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(18*width) + make.centerY.equalToSuperview() + } + view.addSubview(actionViews) + actionViews.snp.makeConstraints { make in + make.top.equalTo(navView.snp.bottom).offset(20*width) + make.left.right.equalToSuperview() + make.height.equalTo(70*width) + } + view.addSubview(tableView) + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(topImageView.snp.bottom).offset(-55*width) + } + //添加一个Label + let label = UILabel() + label.textColor = .white + label.font = .systemFont(ofSize: 20, weight: .regular) + label.text = "Love Lists" + view.addSubview(label) + label.snp.makeConstraints { make in + make.left.equalToSuperview().offset(18*width) + make.bottom.equalTo(tableView.snp.top).offset(-10*width) + } + } + ///生成一个毛玻璃效果View,包含标题和说明 + private func setBlurView() -> UIVisualEffectView { + // 创建一个模糊效果 + let blurEffect = UIBlurEffect(style: .dark) + // 创建一个可交互的毛玻璃视图 + let blurEffectView = UIVisualEffectView(effect: blurEffect) + blurEffectView.alpha = 0.7 + blurEffectView.isUserInteractionEnabled = true + return blurEffectView } - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. + //返回顶部事件View组 + private func showTopView() -> UIView { + let topView = UIView() + topView.backgroundColor = .clear + //添加事件按钮 + let first = actionView(artistsLabel, text: "Love_Artists_logo", tag: 0) + topView.addSubview(first) + first.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.height.equalTo(67*width) + make.width.equalTo(90*width) + make.centerX.equalToSuperview().multipliedBy(0.35) + } + let second = actionView(songsLabel, text: "Love_Song_logo", tag: 1) + topView.addSubview(second) + second.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.height.equalTo(67*width) + make.width.equalTo(90*width) + make.centerX.equalToSuperview() + } + let third = actionView(loadsLabel, text: "Offline_Songs_logo", tag: 2) + topView.addSubview(third) + third.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.height.equalTo(67*width) + make.width.equalTo(90*width) + make.centerX.equalToSuperview().multipliedBy(1.65) + } + topView.isUserInteractionEnabled = true + return topView + } + //生成一个事件View块 + private func actionView(_ label:UILabel, text:String, tag:Int) -> UIView { + let actionView = UIView() + actionView.backgroundColor = .clear + let imageView:UIImageView = .init(image: .init(named: text)) + imageView.contentMode = .scaleAspectFill + actionView.addSubview(imageView) + imageView.snp.makeConstraints { make in + make.top.centerX.equalToSuperview() + } + actionView.addSubview(label) + label.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.width.equalToSuperview().multipliedBy(0.8) + make.bottom.equalToSuperview() + } + actionView.tag = tag + actionView.isUserInteractionEnabled = true + actionView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(actionClick(_:)))) + return actionView + } + //事件点击方法 + @objc private func actionClick(_ sender:UITapGestureRecognizer) { + let tag = sender.view?.tag + switch tag { + case 0: + let loveArtistsVC = MPPositive_LoveArtistsViewController() + navigationController?.pushViewController(loveArtistsVC, animated: true) + case 1: + let loveSongsVC = MPPositive_LoveSongsViewController() + navigationController?.pushViewController(loveSongsVC, animated: true) + default: + let offlineVC = MPPositive_OfflineSongsViewController() + navigationController?.pushViewController(offlineVC, animated: true) + } + } +} +//MARK: - tableView +extension MPPositive_LibraryViewController:UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return MPPositive_LoadCoreModel.shared.listViewModels.count + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_LibraryTableViewCellID, for: indexPath) as! MPPositive_LibraryTableViewCell + cell.listViewModel = MPPositive_LoadCoreModel.shared.listViewModels[indexPath.row] + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let item = MPPositive_LoadCoreModel.shared.listViewModels[indexPath.row] + //列表专辑 + let listVC = MPPositive_ListShowViewController(item.collectionList.browseId ?? "", params: item.collectionList.params ?? "", title: item.title ?? "", subtitle: item.subtitle ?? "") + navigationController?.pushViewController(listVC, animated: true) } - */ - } diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveArtistsViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveArtistsViewController.swift new file mode 100644 index 0000000..5f78c99 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveArtistsViewController.swift @@ -0,0 +1,78 @@ +// +// MPPositive_LoveArtistsViewController.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit + +class MPPositive_LoveArtistsViewController: MPPositive_BaseViewController { + private lazy var numbersLabel:UILabel = createLabel(font: .systemFont(ofSize: 18*width, weight: .regular), textColor: .white, textAlignment: .left) + ///tableView + private lazy var tableView:UITableView = { + let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain) + if #available(iOS 15.0, *) { + tableView.sectionHeaderTopPadding = 0 + } + tableView.backgroundColor = .clear + tableView.separatorStyle = .none + tableView.estimatedRowHeight = 200 + tableView.rowHeight = UITableView.automaticDimension + tableView.dataSource = self + tableView.delegate = self + tableView.register(MPPositive_LoveArtistTableViewCell.self, forCellReuseIdentifier: MPPositive_LoveArtistTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) + return tableView + }() + private let MPPositive_LoveArtistTableViewCellID = "MPPositive_LoveArtistTableViewCell" + override func viewDidLoad() { + super.viewDidLoad() + setTitle("Love Artists") + setPopBtn() + configure() + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + reload() + } + //刷新列表 + private func reload() { + MPPositive_LoadCoreModel.shared.reloadCollectionArtistViewModels { + [weak self] in + guard let self = self else {return} + tableView.showMessage(MPPositive_LoadCoreModel.shared.artistViewModels.count, title: "No Artists") + numbersLabel.text = "\(MPPositive_LoadCoreModel.shared.artistViewModels.count) Artists" + tableView.reloadData() + } + } + private func configure() { + view.addSubview(numbersLabel) + numbersLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(18*width) + make.top.equalTo(navView.snp.bottom).offset(32*width) + } + view.addSubview(tableView) + tableView.snp.makeConstraints { make in + make.top.equalTo(navView.snp.bottom).offset(70*width) + make.left.right.bottom.equalToSuperview() + } + } +} +//MARK: - tableView +extension MPPositive_LoveArtistsViewController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return MPPositive_LoadCoreModel.shared.artistViewModels.count + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_LoveArtistTableViewCellID, for: indexPath) as! MPPositive_LoveArtistTableViewCell + cell.artistViewModel = MPPositive_LoadCoreModel.shared.artistViewModels[indexPath.row] + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let item = MPPositive_LoadCoreModel.shared.artistViewModels[indexPath.row] + //列表专辑 + let artistVC = MPPositive_ArtistShowViewController(item.collectionArtist.artistId ?? "") + navigationController?.pushViewController(artistVC, animated: true) + } +} diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveSongsViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveSongsViewController.swift new file mode 100644 index 0000000..db5df82 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_LoveSongsViewController.swift @@ -0,0 +1,92 @@ +// +// MPPositive_LoveSongsViewController.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit + +class MPPositive_LoveSongsViewController: MPPositive_BaseViewController { + private lazy var numbersLabel:UILabel = createLabel(font: .systemFont(ofSize: 18*width, weight: .regular), textColor: .white, textAlignment: .left) + ///tableView + private lazy var tableView:UITableView = { + let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain) + if #available(iOS 15.0, *) { + tableView.sectionHeaderTopPadding = 0 + } + tableView.backgroundColor = .clear + tableView.separatorStyle = .none + tableView.estimatedRowHeight = 200 + tableView.rowHeight = UITableView.automaticDimension + tableView.dataSource = self + tableView.delegate = self + tableView.register(MPPositive_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) + return tableView + }() + private let MPPositive_SearchResultShowTableViewCellID = "MPPositive_SearchResultShowTableViewCell" + override func viewDidLoad() { + super.viewDidLoad() + setTitle("Love Songs") + setPopBtn() + configure() + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + reload() + } + //刷新列表 + private func reload() { + MPPositive_LoadCoreModel.shared.reloadCollectionSongViewModel { + [weak self] in + guard let self = self else {return} + tableView.showMessage(MPPositive_LoadCoreModel.shared.songViewModels.count, title: "No Songs") + numbersLabel.text = "\(MPPositive_LoadCoreModel.shared.songViewModels.count) Songs" + tableView.reloadData() + } + } + private func configure() { + view.addSubview(numbersLabel) + numbersLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(18*width) + make.top.equalTo(navView.snp.bottom).offset(32*width) + } + view.addSubview(tableView) + tableView.snp.makeConstraints { make in + make.top.equalTo(navView.snp.bottom).offset(70*width) + make.left.right.bottom.equalToSuperview() + } + } +} +//MARK: - tableView +extension MPPositive_LoveSongsViewController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return MPPositive_LoadCoreModel.shared.songViewModels.count + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell + cell.songViewModel = MPPositive_LoadCoreModel.shared.songViewModels[indexPath.row] + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + //将当前收藏音乐放入列表中 + var array:[MPPositive_SongItemModel] = [] + for (index,song) in MPPositive_LoadCoreModel.shared.songViewModels.enumerated() { + let item = MPPositive_SongItemModel() + //填补item数据 + item.index = index + item.reviewUrls = [song.coverURL.absoluteString] + item.title = song.title + item.shortBylineText = song.subtitle + item.videoId = song.collectionSong.videoId + item.lyricsID = song.collectionSong.lyricsID + item.relatedID = song.collectionSong.relatedID + array.append(item) + } + let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: MPPositive_LoadCoreModel.shared.songViewModels[indexPath.row].collectionSong.videoId ?? "") + lodaViewModel.improveData(MPPositive_LoadCoreModel.shared.songViewModels[indexPath.row].collectionSong.videoId ?? "") + MP_PlayerManager.shared.loadPlayer = lodaViewModel + NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) + } +} diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift new file mode 100644 index 0000000..283f264 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift @@ -0,0 +1,95 @@ +// +// MPPositive_OfflineSongsViewController.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit + +class MPPositive_OfflineSongsViewController: MPPositive_BaseViewController { + private lazy var numbersLabel:UILabel = createLabel(font: .systemFont(ofSize: 18*width, weight: .regular), textColor: .white, textAlignment: .left) + ///tableView + private lazy var tableView:UITableView = { + let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain) + if #available(iOS 15.0, *) { + tableView.sectionHeaderTopPadding = 0 + } + tableView.backgroundColor = .clear + tableView.separatorStyle = .none + tableView.estimatedRowHeight = 200 + tableView.rowHeight = UITableView.automaticDimension + tableView.dataSource = self + tableView.delegate = self + tableView.register(MPPositive_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) + return tableView + }() + private let MPPositive_SearchResultShowTableViewCellID = "MPPositive_SearchResultShowTableViewCell" + override func viewDidLoad() { + super.viewDidLoad() + setTitle("Offline Songs") + setPopBtn() + configure() + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + reload() + } + //刷新列表 + private func reload() { + MPPositive_LoadCoreModel.shared.reloadLoadSongViewModel { + [weak self] in + guard let self = self else {return} + tableView.showMessage(MPPositive_LoadCoreModel.shared.loadViewModels.count, title: "No Songs") + numbersLabel.text = "\(MPPositive_LoadCoreModel.shared.loadViewModels.count) Songs" + tableView.reloadData() + } + } + private func configure() { + view.addSubview(numbersLabel) + numbersLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(18*width) + make.top.equalTo(navView.snp.bottom).offset(32*width) + } + view.addSubview(tableView) + tableView.snp.makeConstraints { make in + make.top.equalTo(navView.snp.bottom).offset(70*width) + make.left.right.bottom.equalToSuperview() + } + } +} +//MARK: - tableView +extension MPPositive_OfflineSongsViewController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return MPPositive_LoadCoreModel.shared.loadViewModels.count + } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell + cell.loadViewModel = MPPositive_LoadCoreModel.shared.loadViewModels[indexPath.row] + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + //将当前下载音乐放入列表中 + var array:[MPPositive_SongItemModel] = [] + for (index, song) in MPPositive_LoadCoreModel.shared.loadViewModels.enumerated() { + let item = MPPositive_SongItemModel() + item.index = index + item.coverUrls = [song.loadItem.coverImage] + item.reviewUrls = [song.loadItem.reviewImage] + item.title = song.loadItem.title + item.longBylineText = song.loadItem.longBylineText + item.lengthText = song.loadItem.lengthText + item.shortBylineText = song.loadItem.shortBylineText + item.lyricsID = song.loadItem.lyricsID + item.lyrics = song.loadItem.lyrics + item.videoId = song.loadItem.videoId + item.relatedID = song.loadItem.relatedID + array.append(item) + } + let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: MPPositive_LoadCoreModel.shared.loadViewModels[indexPath.row].loadItem.videoId ?? "") + lodaViewModel.improveData(MPPositive_LoadCoreModel.shared.loadViewModels[indexPath.row].loadItem.videoId ?? "") + MP_PlayerManager.shared.loadPlayer = lodaViewModel + NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) + } +} diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ArtistShowViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ArtistShowViewController.swift index 9399f7f..aa42905 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ArtistShowViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ArtistShowViewController.swift @@ -178,7 +178,7 @@ extension MPPositive_ArtistShowViewController: JXPagingViewDelegate{ navigationController?.pushViewController(artistVC, animated: true) case .list: //列表专辑 - let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: "") + let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: "", title: item.title ?? "", subtitle: item.subtitle ?? "") navigationController?.pushViewController(listVC, animated: true) case .single: //单曲/视频跳转 diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_HomeViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_HomeViewController.swift index 26d6206..4bbaee5 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_HomeViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_HomeViewController.swift @@ -31,6 +31,7 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{ tableView.dataSource = self tableView.delegate = self tableView.register(MPPositive_HomeShowTableViewCell.self, forCellReuseIdentifier: MPPositive_HomeShowTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) return tableView }() private let MPPositive_HomeShowTableViewCellID = "MPPositive_HomeShowTableViewCell" @@ -90,7 +91,17 @@ extension MPPositive_HomeViewController: UITableViewDataSource, UITableViewDeleg func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_HomeShowTableViewCellID, for: indexPath) as! MPPositive_HomeShowTableViewCell cell.browseViewModel = MPPositive_BrowseLoadViewModel.shared.browseModuleLists[indexPath.section] - cell.showType = .init(rawValue: (indexPath.section > 4 ? 4:indexPath.section))! + if MPPositive_BrowseLoadViewModel.shared.browseModuleLists[indexPath.section].items.first?.browseItem.itemType == .single { + //是单曲视频 + if MPPositive_BrowseLoadViewModel.shared.browseModuleLists[indexPath.section].items.first?.browseItem.pageType == "MUSIC_VIDEO_TYPE_OMV" { + cell.showType = .Fifth + }else { + cell.showType = .Frist + } + }else { + //是列表艺术家,当前只作为列表专辑使用 + cell.showType = .Third + } cell.requestNextBlock = { [weak self] (item) in guard let self = self else {return} @@ -108,7 +119,7 @@ extension MPPositive_HomeViewController: UITableViewDataSource, UITableViewDeleg } case .list: //列表专辑 - let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: item.browseItem.params ?? "") + let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: item.browseItem.params ?? "", title: item.title ?? "", subtitle: item.subtitle ?? "") navigationController?.pushViewController(listVC, animated: true) default: break diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift index b734386..c28fed0 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_ListShowViewController.swift @@ -64,6 +64,7 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController { tableView.dataSource = self tableView.delegate = self tableView.register(MPPositive_MusicItemShowTableViewCell.self, forCellReuseIdentifier: MPPositive_MusicItemShowTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) return tableView }() private let MPPositive_MusicItemShowTableViewCellID = "MPPositive_MusicItemShowTableViewCell" @@ -81,16 +82,20 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController { } } - var browseid:String = "" - var params:String = "" + private var browseid:String! + private var params:String! + private var centerTtitle:String! + private var subtitle:String! /// 生成列表控制器 /// - Parameters: /// - browseId: 列表的id /// - params: 列表的编码 - init(_ browseId:String, params:String) { - super.init(nibName: nil, bundle: nil) + init(_ browseId:String, params:String, title:String, subtitle:String) { self.browseid = browseId self.params = params + self.centerTtitle = title + self.subtitle = subtitle + super.init(nibName: nil, bundle: nil) //发起网络请求 MP_NetWorkManager.shared.requestAlbumOrListDatas(browseId, params: params) { [weak self] result in guard let self = self else {return} @@ -237,23 +242,24 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController { if self.collectionListBtn.isSelected == true{ self.collectionListBtn.isSelected = false - MPPositive_CollectionListModel.fetch(.init(format: "browseId == %@", self.browseid)).forEach { i in - if i.browseId == self.browseid{ - if i.params == self.params{ - MPPositive_CollectionListModel.delete(i) - } + MPPositive_CollectionListModel.fetch(.init(format: "browseId == %@", self.browseid)).forEach { i in + if i.browseId == self.browseid{ + if i.params == self.params{ + MPPositive_CollectionListModel.delete(i) } } - + } + MPPositive_LoadCoreModel.shared.reloadCollectionListViewModels(nil) }else{ self.collectionListBtn.isSelected = true let item = MPPositive_CollectionListModel.create() - item.title = listOrAlbum.header.title + item.title = self.centerTtitle + item.subtitle = self.subtitle item.browseId = self.browseid item.params = self.params item.coverImage = listOrAlbum.header.coverUrl MPPositive_CollectionListModel.save() - + MPPositive_LoadCoreModel.shared.reloadCollectionListViewModels(nil) } } //更改当前列表播放状态 diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_MoreContentViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_MoreContentViewController.swift index d675b28..857cd7a 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_MoreContentViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Home(首页,各项列表页,艺术家页)/MPPositive_MoreContentViewController.swift @@ -45,6 +45,7 @@ class MPPositive_MoreContentViewController: MPPositive_BaseViewController { collectionView.delegate = self collectionView.register(MPPositive_MoreListContentCollectionViewCell.self, forCellWithReuseIdentifier: MPPositive_MoreListContentCollectionViewCellID) collectionView.register(MPPositive_HomeListFifthCollectionViewCell.self, forCellWithReuseIdentifier: MPPositive_HomeListFifthCollectionViewCellID) + collectionView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) return collectionView }() //列表cell diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerListShowViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerListShowViewController.swift index 41a7d6c..46b2d1f 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerListShowViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerListShowViewController.swift @@ -21,6 +21,7 @@ class MPPositive_PlayerListShowViewController: UIViewController { tableView.dataSource = self tableView.delegate = self tableView.register(MPPositive_PlayerListShowTableViewCell.self, forCellReuseIdentifier: MPPositive_PlayerListShowTableViewCellID) + return tableView }() private let MPPositive_PlayerListShowTableViewCellID = "MPPositive_PlayerListShowTableViewCell" diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift index 4dd27b7..f59e693 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_PlayerViewController.swift @@ -133,14 +133,11 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont coverView.sliderView.value = Float(value) } //当缓存变化时 - MP_PlayerManager.shared.cacheValueBlock = { [weak self] (value, duration) in + MP_PlayerManager.shared.cacheValueBlock = { [weak self] progress in guard let self = self else { return } - if value < duration { - //进度缓存中 - let float = value/duration - coverView.progressView.setProgress(Float(float), animated: false) + if progress <= 1 { + coverView.progressView.setProgress(progress, animated: false) }else { - //进度缓存满了 coverView.progressView.setProgress(1, animated: false) } } @@ -165,7 +162,6 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont if MP_PlayerManager.shared.loadPlayer.currentVideo != nil { uploadUI() } - coverView.restoreDownloadProgress() } //视图配置 private func configure() { @@ -311,6 +307,7 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont lyricsView.lyricsLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.lyrics?.isEmpty == true ? "No Lyrics":MP_PlayerManager.shared.loadPlayer.currentVideo?.lyrics coverView.loadBtn.isSelected = MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false coverView.collectionSongBtn.isSelected = MP_PlayerManager.shared.loadPlayer.currentVideo?.isCollection ?? false + coverView.restoreDownloadProgress() } //MARK: - 通知 //播放器音乐刷新 diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_RecommendViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_RecommendViewController.swift index 5034117..835505e 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_RecommendViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Player(播放器)/MPPositive_RecommendViewController.swift @@ -51,6 +51,7 @@ class MPPositive_RecommendViewController: MPPositive_BaseViewController { collectionView.dataSource = self collectionView.delegate = self collectionView.register(MPPositive_RecommendMemberCollectionViewCell.self, forCellWithReuseIdentifier: MPPositive_RecommendMemberCollectionViewCellID) + return collectionView }() private let MPPositive_RecommendMemberCollectionViewCellID = "MPPositive_RecommendMemberCollectionViewCell" @@ -190,7 +191,7 @@ extension MPPositive_RecommendViewController: JXSegmentedListContainerViewDataSo switch item.browseItem.itemType { case .list: //列表专辑 - let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: "") + let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: "", title: item.title ?? "", subtitle: item.subtitle ?? "") navigationController?.pushViewController(listVC, animated: true) case .single: //单曲/视频跳转 diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchResultShowViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchResultShowViewController.swift index 4e14d43..a4f71ef 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchResultShowViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchResultShowViewController.swift @@ -61,6 +61,21 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController { private lazy var suggestionView:MPPositive_SearchSuggestionsView = .init(frame: .zero) //MARK: - 结果展示View private lazy var resultsShowView:MPPositive_SearchResultsShowView = .init(frame: .zero) + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + init(_ text:String) { + super.init(nibName: nil, bundle: nil) + searchTextField.text = text + searchText = text + resultsShowView.loadModel = .init(text) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + override func viewDidLoad() { super.viewDidLoad() setTitle("Result") @@ -88,7 +103,7 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController { navigationController?.pushViewController(artistVC, animated: true) case .list: //列表专辑 - let listVC = MPPositive_ListShowViewController(item.item.browseId ?? "", params: "") + let listVC = MPPositive_ListShowViewController(item.item.browseId ?? "", params: "", title: item.title ?? "", subtitle: item.subtitle ?? "") navigationController?.pushViewController(listVC, animated: true) case .single: //单曲/视频跳转 diff --git a/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchViewController.swift b/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchViewController.swift index 9d4121e..d866723 100644 --- a/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchViewController.swift +++ b/MusicPlayer/MP/MPPositive/ViewControllers/Search(搜索页)/MPPositive_SearchViewController.swift @@ -14,6 +14,28 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController { imageView.contentMode = .scaleAspectFill return imageView }() + private lazy var collectionView:UICollectionView = { + let layout = MPPositive_TagFlowLayout() + layout.delegate = self + let collectionView:UICollectionView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), collectionViewLayout: layout) + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = .clear + collectionView.dataSource = self + collectionView.delegate = self + collectionView.register(MPPositive_SearchTagCollectionViewCell.self, forCellWithReuseIdentifier: MPPositive_SearchTagCollectionViewCellID) + return collectionView + }() + private let MPPositive_SearchTagCollectionViewCellID = "MPPositive_SearchTagCollectionViewCell" + //历史标签 + private lazy var historyLabel:UILabel = createLabel("History", font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .init(hex: "#666666"), textAlignment: .left) + //删除按钮 + private lazy var deleteBtn:UIButton = { + let btn:UIButton = .init() + btn.setBackgroundImage(UIImage(named: "Tag_Delete'logo"), for: .normal) + btn.addTarget(self, action: #selector(deleteClick(_ :)), for: .touchUpInside) + return btn + }() override func viewDidLoad() { super.viewDidLoad() setTitle("") @@ -22,6 +44,7 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController { } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + collectionView.reloadData() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) @@ -40,6 +63,23 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController { make.top.right.left.equalToSuperview() make.height.equalTo(981*width) } + view.addSubview(deleteBtn) + deleteBtn.snp.makeConstraints { make in + make.width.height.equalTo(24*width) + make.right.equalToSuperview().offset(-18*width) + make.top.equalTo(navView.snp.bottom).offset(24*width) + } + view.addSubview(historyLabel) + historyLabel.snp.makeConstraints { make in + make.centerY.equalTo(deleteBtn) + make.left.equalToSuperview().offset(18*width) + } + view.addSubview(collectionView) + collectionView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalTo(deleteBtn.snp.bottom).offset(6*width) + make.height.equalTo(130*width) + } } //生成一个顶部搜索框 private func createSearchView() -> UIView{ @@ -71,5 +111,124 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController { let resultVC = MPPositive_SearchResultShowViewController() navigationController?.pushViewController(resultVC, animated: false) } + //删除按钮 + @objc private func deleteClick(_ sender:UIButton) { + //移除所有 + MPPositive_SearchTagModel.fetchAll().forEach { item in + MPPositive_SearchTagModel.delete(item) + } + MPPositive_LoadCoreModel.shared.reloadSearchTags { + [weak self] in + guard let self = self else {return} + collectionView.reloadData() + } + } +} +//MARK: - collectionView +extension MPPositive_SearchViewController: UICollectionViewDataSource, UICollectionViewDelegate, MPPositive_TagLayoutDelegate { + + func waterFlowLayout(_ layout: MPPositive_TagFlowLayout, indexPath: IndexPath) -> CGFloat { + let text = MPPositive_LoadCoreModel.shared.searchTags[indexPath.row].text ?? "" + let textWidth = text.textAutoWidth(height: 11.5 * width, font: .systemFont(ofSize: 12 * width, weight: .medium)) + (43 * width) + return textWidth + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return MPPositive_LoadCoreModel.shared.searchTags.count > 10 ? 10:MPPositive_LoadCoreModel.shared.searchTags.count + } + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MPPositive_SearchTagCollectionViewCellID, for: indexPath) as! MPPositive_SearchTagCollectionViewCell + cell.setText(MPPositive_LoadCoreModel.shared.searchTags[indexPath.row].text) + return cell + } + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let text = MPPositive_LoadCoreModel.shared.searchTags[indexPath.row].text ?? "" + let resultVC = MPPositive_SearchResultShowViewController(text) + navigationController?.pushViewController(resultVC, animated: false) + } +} +//MARK: - 宽度代理 +protocol MPPositive_TagLayoutDelegate { + func waterFlowLayout(_ layout:MPPositive_TagFlowLayout,indexPath:IndexPath) -> CGFloat +} +//MARK: - 重写布局 +///标签layout +class MPPositive_TagFlowLayout: UICollectionViewFlowLayout { + //固定行高 + var rowHeight:CGFloat = 22 * width + //宽度获取代理 + var delegate:MPPositive_TagLayoutDelegate? + //x值数组 + var originxArray:[CGFloat]! + //y值数组 + var originyArray:[CGFloat]! + override init() { + super.init() + //列间距 + minimumInteritemSpacing = 8 * width + //行间距 + minimumLineSpacing = 8 * width + //内边距 + sectionInset = .init(top: 5 * width, left: 18 * width, bottom: 5 * width, right: 18 * width) + scrollDirection = .vertical + //初始化数组 + originxArray = [CGFloat]() + originyArray = [CGFloat]() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + //MARK: - 重写父类方法,实现瀑布流布局 + //当尺寸发生变化时,刷新布局 + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + return true + } + override func prepare() { + super.prepare() + } + //处理所有item的layoutAttributes + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + let array = super.layoutAttributesForElements(in: rect) + var mutArray = [UICollectionViewLayoutAttributes]() + array?.forEach({ (attrs) in + let theAttrs = layoutAttributesForItem(at: attrs.indexPath) + mutArray.append(theAttrs!) + }) + return mutArray + } + //处理单个item的layoutAttributes + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + //起始x值 + var x = sectionInset.left + var y = sectionInset.top + //判断获得前一个cell的x和y + let preRow = indexPath.row - 1 + if preRow >= 0 { + //不是第一个cell + if originyArray.count > preRow { + //更新x和y值 + x = originxArray[preRow] + y = originyArray[preRow] + } + let preIndexPath = IndexPath(item: preRow, section: indexPath.section) + //计算宽度 + let preWidth = delegate?.waterFlowLayout(self, indexPath: preIndexPath) + x += preWidth! + minimumInteritemSpacing + } + var currentWidth = delegate?.waterFlowLayout(self, indexPath: indexPath) + //保证每一个cell不超过最大宽度 + currentWidth = min(currentWidth!, collectionView!.frame.size.width - sectionInset.left - sectionInset.right) + if x + currentWidth! > collectionView!.frame.size.width - sectionInset.right { + //超出范围,换行 + x = self.sectionInset.left + y += rowHeight + minimumLineSpacing + } + //创建属性 + let attrs = UICollectionViewLayoutAttributes.init(forCellWith: indexPath) + attrs.frame = CGRect(x: x, y: y, width: currentWidth!, height: rowHeight) + originxArray.insert(x, at: indexPath.row) + originyArray.insert(y, at: indexPath.row) + return attrs + } } - diff --git a/MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LibraryTableViewCell.swift b/MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LibraryTableViewCell.swift new file mode 100644 index 0000000..ddfe569 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LibraryTableViewCell.swift @@ -0,0 +1,84 @@ +// +// MPPositive_LibraryTableViewCell.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit + +class MPPositive_LibraryTableViewCell: UITableViewCell { + //特殊图片(展示预览图片) + private lazy var iconImageView:UIImageView = { + let imageView:UIImageView = .init() + imageView.contentMode = .scaleAspectFill + imageView.layer.masksToBounds = true + return imageView + }() + private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 14*width, weight: .medium), textColor: .white, textAlignment: .left) + private lazy var subtitleLabel:UILabel = createLabel(font: .systemFont(ofSize: 12*width, weight: .regular), textColor: .init(hex: "#FFFFFF", alpha: 0.5), textAlignment: .left) + ///更多按钮 + private lazy var moreBtn:UIButton = { + let btn:UIButton = .init() + btn.setBackgroundImage(UIImage(named: "Song_More'logo"), for: .normal) + btn.addTarget(self, action: #selector(moreActionClick(_ :)), for: .touchUpInside) + return btn + }() + var listViewModel:MPPositive_CollectionListViewModel!{ + didSet{ + listViewModel.setImage(iconImageView) + titleLabel.text = listViewModel.title + subtitleLabel.text = listViewModel.subtitle + } + } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + configure() + } + required init?(coder: NSCoder) { + super.init(coder: coder) + } + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + private func configure() { + contentView.addSubview(iconImageView) + iconImageView.snp.makeConstraints { make in + make.width.height.equalTo(50*width) + make.top.equalToSuperview().offset(6*width).priority(999) + make.bottom.equalToSuperview().offset(-6*width) + make.left.equalToSuperview().offset(18*width) + } + contentView.addSubview(moreBtn) + moreBtn.snp.makeConstraints { make in + make.width.height.equalTo(24*width) + make.centerY.equalTo(iconImageView.snp.centerY) + make.right.equalToSuperview().offset(-18*width) + } + contentView.addSubview(titleLabel) + titleLabel.snp.makeConstraints { make in + make.top.equalTo(iconImageView.snp.top).offset(10*width) + make.left.equalTo(iconImageView.snp.right).offset(12*width) + make.right.equalTo(moreBtn.snp.left).offset(-10*width) + } + contentView.addSubview(subtitleLabel) + subtitleLabel.snp.makeConstraints { make in + make.bottom.equalTo(iconImageView.snp.bottom).offset(-10*width) + make.left.equalTo(titleLabel.snp.left) + make.right.equalTo(titleLabel.snp.right) + } + } + //点击更多 + @objc private func moreActionClick(_ sender:UIButton) { + + } +} diff --git a/MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LoveArtistTableViewCell.swift b/MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LoveArtistTableViewCell.swift new file mode 100644 index 0000000..076874e --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Views/Center/MPPositive_LoveArtistTableViewCell.swift @@ -0,0 +1,60 @@ +// +// MPPositive_LoveArtistTableViewCell.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/28. +// + +import UIKit + +class MPPositive_LoveArtistTableViewCell: UITableViewCell { + //特殊图片(展示预览图片) + private lazy var iconImageView:UIImageView = { + let imageView:UIImageView = .init() + imageView.contentMode = .scaleAspectFill + imageView.layer.masksToBounds = true + return imageView + }() + private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 14*width, weight: .medium), textColor: .white, textAlignment: .left) + var artistViewModel:MPPositive_CollectionArtistViewModel!{ + didSet{ + artistViewModel.setImage(iconImageView) + titleLabel.text = artistViewModel.title + } + } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + backgroundColor = .clear + configure() + } + required init?(coder: NSCoder) { + super.init(coder: coder) + } + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + private func configure() { + contentView.addSubview(iconImageView) + iconImageView.snp.makeConstraints { make in + make.width.height.equalTo(50*width) + make.top.equalToSuperview().offset(8*width).priority(999) + make.bottom.equalToSuperview().offset(-8*width) + make.left.equalToSuperview().offset(18*width) + } + contentView.addSubview(titleLabel) + titleLabel.snp.makeConstraints { make in + make.top.equalTo(iconImageView.snp.top).offset(10*width) + make.left.equalTo(iconImageView.snp.right).offset(12*width) + make.right.equalToSuperview().offset(-12*width) + } + } + +} diff --git a/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowHeaderView.swift b/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowHeaderView.swift index d5494a5..33a4cb8 100644 --- a/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowHeaderView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowHeaderView.swift @@ -102,13 +102,13 @@ class MPPositive_ArtistShowHeaderView: UIView { // MPPositive_CollectionArtistModel.delete(i) // } // } - let items = MPPositive_CollectionArtistModel.fetch(.init(format: "artistId == %@", self.artistid)) + let items = MPPositive_CollectionArtistModel.fetch(.init(format: "artistId == %@", self.artistid)) for item in items { if item.artistId == self.artistid { MPPositive_CollectionArtistModel.delete(item) } } - + MPPositive_LoadCoreModel.shared.reloadCollectionArtistViewModels(nil) }else{ self.collectionBtn.isSelected = true @@ -118,7 +118,7 @@ class MPPositive_ArtistShowHeaderView: UIView { item.subtitle = artist.header.subscriptionedText item.artistId = self.artistid MPPositive_CollectionArtistModel.save() - + MPPositive_LoadCoreModel.shared.reloadCollectionArtistViewModels(nil) } } diff --git a/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowTypeView.swift b/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowTypeView.swift index 83a4573..fab2cb4 100644 --- a/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowTypeView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_ArtistShowTypeView.swift @@ -23,6 +23,7 @@ class MPPositive_ArtistShowTypeView: UIView, JXPagingViewListViewDelegate { tableView.register(MPPositive_ArtistShowSongTableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistShowSongTableViewCellID) tableView.register(MPPositive_ArtistShowListableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistShowListableViewCellID) tableView.register(MPPositive_ArtistDescriptionTableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistDescriptionTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) //添加一个上拉加载 let footer = MJRefreshAutoGifFooter { [weak self] in diff --git a/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_MusicItemShowTableViewCell.swift b/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_MusicItemShowTableViewCell.swift index 95ef988..3330017 100644 --- a/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_MusicItemShowTableViewCell.swift +++ b/MusicPlayer/MP/MPPositive/Views/Home/MPPositive_MusicItemShowTableViewCell.swift @@ -28,13 +28,13 @@ class MPPositive_MusicItemShowTableViewCell: UITableViewCell { return btn }() ///下载状态按钮 - private lazy var loadBtn:UIButton = { - let btn:UIButton = .init() - btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal) - btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected) - btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside) - return btn - }() +// private lazy var loadBtn:UIButton = { +// let btn:UIButton = .init() +// btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal) +// btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected) +// btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside) +// return btn +// }() //音乐实体 var itemView:MPPositive_BrowseItemViewModel!{ didSet{ @@ -81,17 +81,17 @@ class MPPositive_MusicItemShowTableViewCell: UITableViewCell { make.centerY.equalTo(coverImageView.snp.centerY) make.right.equalToSuperview().offset(-18*width) } - contentView.addSubview(loadBtn) - loadBtn.snp.makeConstraints { make in - make.width.height.equalTo(24*width) - make.centerY.equalTo(coverImageView.snp.centerY) - make.right.equalToSuperview().offset(-54*width) - } +// contentView.addSubview(loadBtn) +// loadBtn.snp.makeConstraints { make in +// make.width.height.equalTo(24*width) +// make.centerY.equalTo(coverImageView.snp.centerY) +// make.right.equalToSuperview().offset(-54*width) +// } contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(coverImageView.snp.top).offset(10*width) make.left.equalTo(coverImageView.snp.right).offset(12*width) - make.right.equalTo(loadBtn.snp.left).offset(-12*width) + make.right.equalTo(moreBtn.snp.left).offset(-12*width) } contentView.addSubview(subtitleLabel) subtitleLabel.snp.makeConstraints { make in diff --git a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift index 29ef4c3..1470c9e 100644 --- a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_PlayerCoverView.swift @@ -51,6 +51,7 @@ class MPPositive_PlayerCoverView: UIView { progressView.isUserInteractionEnabled = true progressView.progressTintColor = .init(hex: "#FFFFFF", alpha: 0.3) progressView.trackTintColor = .clear + progressView.progress = 0 return progressView }() ///当前播放时间值Label @@ -72,24 +73,15 @@ class MPPositive_PlayerCoverView: UIView { } return maskView }() - - - override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .clear configure() - - - //添加监听 NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkNotReachableAction(_:)), notificationName: .net_switch_notReachable) NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkReachableAction(_:)), notificationName: .net_switch_reachable) } - - - required init?(coder: NSCoder) { super.init(coder: coder) // NotificationCenter.default.addObserver(self, selector: #selector(updateProgress(_:)), name: Notification.Name("DownloadProgressUpdated"), object: nil) @@ -100,20 +92,26 @@ class MPPositive_PlayerCoverView: UIView { public func restoreDownloadProgress() { - if let currentVideo = MP_PlayerManager.shared.loadPlayer.currentVideo, let videoURLString = currentVideo.song.resourceUrls?.first, let videoURL = URL(string: videoURLString) { if let progress = DownloadManager.shared.getProgress(for: videoURL) { + //判断当前是正在下载的VideoID addCircularProgressBar(over: loadBtn) loadView.setProgress(to: progress) -// DispatchQueue.main.asyncAfter(deadline: .now() + 2) { // 延迟0.1秒 self.layoutIfNeeded() self.loadBtn.setBackgroundImage(UIImage(named: ""), for: .normal) self.loadBtn.setImage(UIImage(named: "download"), for: .normal) - self.addCircularProgressBar(over: self.loadBtn) - self.loadView.setProgress(to: progress) -// } + self.addCircularProgressBar(over: self.loadBtn) + self.loadView.setProgress(to: progress) + }else { + //不是当前下载的videoID + if (loadView.superview) != nil { + loadView.removeFromSuperview() + } + self.loadBtn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal) + self.loadBtn.setImage(UIImage(), for: .normal) + self.loadBtn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected) } } } @@ -135,10 +133,6 @@ class MPPositive_PlayerCoverView: UIView { } } - - - - deinit { NotificationCenter.default.removeObserver(self) } @@ -257,6 +251,7 @@ class MPPositive_PlayerCoverView: UIView { } } MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad() + MPPositive_LoadCoreModel.shared.reloadCollectionSongViewModel(nil) } }else{ self.collectionSongBtn.isSelected = true @@ -266,8 +261,11 @@ class MPPositive_PlayerCoverView: UIView { item.videoId = MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId item.subtitle = MP_PlayerManager.shared.loadPlayer.currentVideo.subtitle item.coverImage = MP_PlayerManager.shared.loadPlayer.currentVideo.coverUrl + item.lyricsID = MP_PlayerManager.shared.loadPlayer.currentVideo.song.lyricsID + item.relatedID = MP_PlayerManager.shared.loadPlayer.currentVideo.song.relatedID MPPositive_CollectionSongModel.save() MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad() + MPPositive_LoadCoreModel.shared.reloadCollectionSongViewModel(nil) } } } @@ -281,26 +279,26 @@ class MPPositive_PlayerCoverView: UIView { if let currentVideo = MP_PlayerManager.shared.loadPlayer.currentVideo, let videoURLString = currentVideo.song.resourceUrls?.first, let videoURL = URL(string: videoURLString) { - let videoId = currentVideo.song.videoId ?? "default_video_id" - DownloadManager.shared.downloadVideo(from: videoURL, videoId: videoId, progressHandler: { [weak self] progress in + DownloadManager.shared.downloadVideo(from: videoURL, song: MP_PlayerManager.shared.loadPlayer.currentVideo.song, progressHandler: { [weak self] progress in DispatchQueue.main.async { self?.loadView.setProgress(to: progress) NotificationCenter.default.post(name: Notification.Name("DownloadProgressUpdated"), object: nil, userInfo: ["url": videoURL, "progress": progress]) } }, completion: { [weak self] result in switch result { - case .success(let fileURL): + case .success(let song): let item = MPPositive_DownloadItemModel.create() - item.resourcePath = "\(fileURL)" - item.coverImage = URL(string: MP_PlayerManager.shared.loadPlayer.currentVideo.song.coverUrls!.first!) - item.reviewImage = URL(string: MP_PlayerManager.shared.loadPlayer.currentVideo.song.reviewUrls!.first!) - item.title = MP_PlayerManager.shared.loadPlayer.currentVideo.song.title - item.longBylineText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.longBylineText - item.lengthText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.lengthText - item.shortBylineText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.shortBylineText - item.lyrics = MP_PlayerManager.shared.loadPlayer.currentVideo.lyrics - item.videoId = MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId - item.relatedID = MP_PlayerManager.shared.loadPlayer.currentVideo.song.relatedID + //检索 + item.coverImage = song.coverUrls!.last + item.reviewImage = song.reviewUrls!.last + item.title = song.title + item.longBylineText = song.longBylineText + item.lengthText = song.lengthText + item.shortBylineText = song.shortBylineText + item.lyrics = song.lyrics + item.lyricsID = song.lyricsID + item.videoId = song.videoId + item.relatedID = song.relatedID MPPositive_DownloadItemModel.save() DispatchQueue.main.async { @@ -308,6 +306,8 @@ class MPPositive_PlayerCoverView: UIView { MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad() self?.loadBtn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .normal) self?.loadBtn.setImage(UIImage(named: ""), for: .normal) + print("完成了对\(song.title ?? "")的下载") + MPPositive_LoadCoreModel.shared.reloadLoadSongViewModel(nil) } case .failure(let error): print("Download failed with error: \(error)") diff --git a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_RecommendShowTypeView.swift b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_RecommendShowTypeView.swift index c7d9acf..dd4573e 100644 --- a/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_RecommendShowTypeView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Player/MPPositive_RecommendShowTypeView.swift @@ -22,6 +22,7 @@ class MPPositive_RecommendShowTypeView: UIView, JXSegmentedListContainerViewList tableView.delegate = self tableView.register(MPPositive_ArtistShowSongTableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistShowSongTableViewCellID) tableView.register(MPPositive_ArtistShowListableViewCell.self, forCellReuseIdentifier: MPPositive_ArtistShowListableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) return tableView }() private let MPPositive_ArtistShowSongTableViewCellID = "MPPositive_ArtistShowSongTableViewCell" diff --git a/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultPreviewShowView.swift b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultPreviewShowView.swift index 6067425..e7ea238 100644 --- a/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultPreviewShowView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultPreviewShowView.swift @@ -21,6 +21,7 @@ class MPPositive_SearchResultPreviewShowView: UIView, JXSegmentedListContainerVi tableView.dataSource = self tableView.delegate = self tableView.register(MPPositive_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) return tableView }() private let MPPositive_SearchResultShowTableViewCellID = "MPPositive_SearchResultShowTableViewCell" diff --git a/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultShowTableViewCell.swift b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultShowTableViewCell.swift index 2d2a539..1ab2164 100644 --- a/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultShowTableViewCell.swift +++ b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultShowTableViewCell.swift @@ -24,14 +24,14 @@ class MPPositive_SearchResultShowTableViewCell: UITableViewCell { btn.addTarget(self, action: #selector(moreActionClick(_ :)), for: .touchUpInside) return btn }() - ///下载状态按钮 - private lazy var loadBtn:UIButton = { - let btn:UIButton = .init() - btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal) - btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected) - btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside) - return btn - }() +// ///下载状态按钮 +// private lazy var loadBtn:UIButton = { +// let btn:UIButton = .init() +// btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal) +// btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected) +// btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside) +// return btn +// }() var itemView:MPPositive_SearchResultItemViewModel!{ didSet{ itemView.setImage(iconImageView) @@ -40,13 +40,28 @@ class MPPositive_SearchResultShowTableViewCell: UITableViewCell { //检索类型 if itemView.item.itemType == .single { moreBtn.isHidden = false - loadBtn.isHidden = false +// loadBtn.isHidden = false }else { moreBtn.isHidden = true - loadBtn.isHidden = true +// loadBtn.isHidden = true } } } + var songViewModel:MPPositive_CollectionSongViewModel!{ + didSet{ + songViewModel.setImage(iconImageView) + titleLabel.text = songViewModel.title + subtitleLabel.text = songViewModel.subtitle + } + } + var loadViewModel:MPPositive_DownloadViewModel!{ + didSet{ + loadViewModel.setImage(iconImageView) + titleLabel.text = loadViewModel.title + subtitleLabel.text = loadViewModel.subtitle + } + } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none @@ -81,17 +96,17 @@ class MPPositive_SearchResultShowTableViewCell: UITableViewCell { make.centerY.equalTo(iconImageView.snp.centerY) make.right.equalToSuperview().offset(-18*width) } - contentView.addSubview(loadBtn) - loadBtn.snp.makeConstraints { make in - make.width.height.equalTo(24*width) - make.centerY.equalTo(iconImageView.snp.centerY) - make.right.equalToSuperview().offset(-54*width) - } +// contentView.addSubview(loadBtn) +// loadBtn.snp.makeConstraints { make in +// make.width.height.equalTo(24*width) +// make.centerY.equalTo(iconImageView.snp.centerY) +// make.right.equalToSuperview().offset(-54*width) +// } contentView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalTo(iconImageView.snp.top).offset(10*width) make.left.equalTo(iconImageView.snp.right).offset(12*width) - make.right.equalTo(loadBtn.snp.left).offset(-10*width) + make.right.equalTo(moreBtn.snp.left).offset(-10*width) } contentView.addSubview(subtitleLabel) subtitleLabel.snp.makeConstraints { make in diff --git a/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultTypeShowView.swift b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultTypeShowView.swift index c46e6b4..bd8a81c 100644 --- a/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultTypeShowView.swift +++ b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchResultTypeShowView.swift @@ -22,6 +22,7 @@ class MPPositive_SearchResultTypeShowView: UIView, JXSegmentedListContainerViewL tableView.dataSource = self tableView.delegate = self tableView.register(MPPositive_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID) + tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) //添加一个上拉加载 let footer = MJRefreshAutoGifFooter { [weak self] in diff --git a/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchTagCollectionViewCell.swift b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchTagCollectionViewCell.swift new file mode 100644 index 0000000..8075456 --- /dev/null +++ b/MusicPlayer/MP/MPPositive/Views/Search/MPPositive_SearchTagCollectionViewCell.swift @@ -0,0 +1,35 @@ +// +// MPPositive_SearchTagCollectionViewCell.swift +// MusicPlayer +// +// Created by Mr.Zhou on 2024/5/29. +// + +import UIKit + +class MPPositive_SearchTagCollectionViewCell: UICollectionViewCell { + //标题label + private lazy var titleLabel:UILabel = createLabel("Title", font: .systemFont(ofSize: 12*width, weight: .medium), textColor: .white, textAlignment: .center) + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .init(hex: "#1F1F1F") + layer.masksToBounds = true + layer.cornerRadius = 10*width + confirgue() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + //配置 + private func confirgue() { + addSubview(titleLabel) + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.left.equalToSuperview().offset(12*width) + } + } + func setText(_ text:String) { + titleLabel.text = text + } +} diff --git a/Podfile b/Podfile index 891a368..b29dba9 100644 --- a/Podfile +++ b/Podfile @@ -23,5 +23,8 @@ pod 'JXSegmentedView' pod 'JXPagingView/Paging' #刷新支持 pod 'MJRefresh' - +#流音频播放 +pod 'FreeStreamer' +#下载框架 +pod 'Tiercel' end diff --git a/Podfile.lock b/Podfile.lock index 3302729..d85e74c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,18 +1,23 @@ PODS: - Alamofire (5.9.1) + - FreeStreamer (4.0.0): + - Reachability (~> 3.0) - IQKeyboardManagerSwift (6.5.16) - JXPagingView/Paging (2.1.3) - JXSegmentedView (1.3.3) - Kingfisher (7.11.0) - MJRefresh (3.7.9) + - Reachability (3.7.6) - SnapKit (5.7.1) - SVProgressHUD (2.3.1): - SVProgressHUD/Core (= 2.3.1) - SVProgressHUD/Core (2.3.1) - SwiftDate (6.3.1) + - Tiercel (3.2.5) DEPENDENCIES: - Alamofire + - FreeStreamer - IQKeyboardManagerSwift - JXPagingView/Paging - JXSegmentedView @@ -21,30 +26,37 @@ DEPENDENCIES: - SnapKit - SVProgressHUD - SwiftDate + - Tiercel SPEC REPOS: trunk: - Alamofire + - FreeStreamer - IQKeyboardManagerSwift - JXPagingView - JXSegmentedView - Kingfisher - MJRefresh + - Reachability - SnapKit - SVProgressHUD - SwiftDate + - Tiercel SPEC CHECKSUMS: Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c + FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea IQKeyboardManagerSwift: 12d89768845bb77b55cc092ecc2b1f9370f06b76 JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e JXSegmentedView: 651b60fcf705258ba9395edd53876dbd2853fb68 Kingfisher: b9c985d864d43515f404f1ef4a8ce7d802ace3ac MJRefresh: ff9e531227924c84ce459338414550a05d2aea78 + Reachability: fd0ecd23705e2599e4cceeb943222ae02296cbc6 SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22 SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2 + Tiercel: c0a73f876a72800333b15f4e7e48791f4ad21e90 -PODFILE CHECKSUM: ba88795291c32ea83d380e5384537ca7f5568cd7 +PODFILE CHECKSUM: 3804949e23587f6d341ef21aa5e0b1c55a818968 COCOAPODS: 1.15.2 diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.h new file mode 100644 index 0000000..e89cc26 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.h @@ -0,0 +1,274 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import + +#include "FSAudioStream.h" + +@class FSCheckContentTypeRequest; +@class FSParsePlaylistRequest; +@class FSParseRssPodcastFeedRequest; +@class FSPlaylistItem; +@protocol FSAudioControllerDelegate; + +/** + * FSAudioController is functionally equivalent to FSAudioStream with + * one addition: it can be directly fed with a playlist (PLS, M3U) URL + * or an RSS podcast feed. It determines the content type and forms + * a playlist for playback. Notice that this generates more traffic and + * is generally more slower than using an FSAudioStream directly. + * + * It is also possible to construct a playlist by yourself by providing + * the playlist items. In this case see the methods for managing the playlist. + * + * If you have a playlist with multiple items, FSAudioController attemps + * automatically preload the next item in the playlist. This helps to + * start the next item playback immediately without the need for the + * user to wait for buffering. + * + * Notice that do not attempt to set your own blocks to the audio stream + * owned by the controller. FSAudioController uses the blocks internally + * and any user set blocks will be overwritten. Instead use the blocks + * offered by FSAudioController. + */ +@interface FSAudioController : NSObject { + NSURL *_url; + NSMutableArray *_streams; + + float _volume; + + BOOL _readyToPlay; + + FSCheckContentTypeRequest *_checkContentTypeRequest; + FSParsePlaylistRequest *_parsePlaylistRequest; + FSParseRssPodcastFeedRequest *_parseRssPodcastFeedRequest; + + void (^_onStateChangeBlock)(FSAudioStreamState); + void (^_onMetaDataAvailableBlock)(NSDictionary*); + void (^_onFailureBlock)(FSAudioStreamError error, NSString *errorDescription); +} + +/** + * Initializes the audio stream with an URL. + * + * @param url The URL from which the stream data is retrieved. + */ +- (id)initWithUrl:(NSURL *)url; + +/** + * Starts playing the stream. Before the playback starts, + * the URL content type is checked and playlists resolved. + */ +- (void)play; + +/** + * Starts playing the stream from an URL. Before the playback starts, + * the URL content type is checked and playlists resolved. + * + * @param url The URL from which the stream data is retrieved. + */ +- (void)playFromURL:(NSURL *)url; + +/** + * Starts playing the stream from the given playlist. Each item in the array + * must an FSPlaylistItem. + * + * @param playlist The playlist items. + */ +- (void)playFromPlaylist:(NSArray *)playlist; + +/** + * Starts playing the stream from the given playlist. Each item in the array + * must an FSPlaylistItem. The playback starts from the given index + * in the playlist. + * + * @param playlist The playlist items. + * @param index The playlist index where to start playback from. + */ +- (void)playFromPlaylist:(NSArray *)playlist itemIndex:(NSUInteger)index; + +/** + * Plays a playlist item at the specified index. + * + * @param index The playlist index where to start playback from. + */ +- (void)playItemAtIndex:(NSUInteger)index; + +/** + * Returns the count of playlist items. + */ +- (NSUInteger)countOfItems; + +/** + * Adds an item to the playlist. + * + * @param item The playlist item to be added. + */ +- (void)addItem:(FSPlaylistItem *)item; + +/** + * Adds an item to the playlist at a specific position. + * + * @param item The playlist item to be added. + * @param index The location in the playlist to place the new item + */ +- (void)insertItem:(FSPlaylistItem *)item atIndex:(NSInteger)index; + +/** + * Moves an item already in the playlist to a different position in the playlist + * + * @param from The original index of the track to move + * @param to The destination of the the track at the index specified in `from` + */ +- (void)moveItemAtIndex:(NSUInteger)from toIndex:(NSUInteger)to; + +/** + * Replaces a playlist item. + * + * @param index The index of the playlist item to be replaced. + * @param item The playlist item used the replace the existing one. + */ +- (void)replaceItemAtIndex:(NSUInteger)index withItem:(FSPlaylistItem *)item; + +/** + * Removes a playlist item. + * + * @param index The index of the playlist item to be removed. + */ +- (void)removeItemAtIndex:(NSUInteger)index; + +/** + * Stops the stream playback. + */ +- (void)stop; + +/** + * If the stream is playing, the stream playback is paused upon calling pause. + * Otherwise (the stream is paused), calling pause will continue the playback. + */ +- (void)pause; + +/** + * Returns the playback status: YES if the stream is playing, NO otherwise. + */ +- (BOOL)isPlaying; + +/** + * Returns if the current multiple-item playlist has next item + */ +- (BOOL)hasNextItem; + +/** + * Returns if the current multiple-item playlist has Previous item + */ +- (BOOL)hasPreviousItem; + +/** + * Play the next item of multiple-item playlist + */ +- (void)playNextItem; + +/** + * Play the previous item of multiple-item playlist + */ +- (void)playPreviousItem; + +/** + * This property holds the current playback volume of the stream, + * from 0.0 to 1.0. + * + * Note that the overall volume is still constrained by the volume + * set by the user! So the actual volume cannot be higher + * than the volume currently set by the user. For example, if + * requesting a volume of 0.5, then the volume will be 50% + * lower than the current playback volume set by the user. + */ +@property (nonatomic,assign) float volume; +/** + * The controller URL. + */ +@property (nonatomic,assign) NSURL *url; +/** + * The the active playing stream, which may change + * from time to time during the playback. In this way, do not + * set your own blocks to the stream but use the blocks + * provides by FSAudioController. + */ +@property (readonly) FSAudioStream *activeStream; +/** + * The playlist item the controller is currently using. + */ +@property (nonatomic,readonly) FSPlaylistItem *currentPlaylistItem; + +/** + * This property determines if the next playlist item should be loaded + * automatically. This is YES by default. + */ +@property (nonatomic,assign) BOOL preloadNextPlaylistItemAutomatically; + +/** + * This property determines if the debug output is enabled. Disabled + * by default + */ +@property (nonatomic,assign) BOOL enableDebugOutput; + +/** + * This property determines if automatic audio session handling is enabled. + * This is YES by default. + */ +@property (nonatomic,assign) BOOL automaticAudioSessionHandlingEnabled; + +/** + * This property holds the configuration used for the streaming. + */ +@property (nonatomic,strong) FSStreamConfiguration *configuration; + +/** + * Called upon a state change. + */ +@property (copy) void (^onStateChange)(FSAudioStreamState state); +/** + * Called upon a meta data is available. + */ +@property (copy) void (^onMetaDataAvailable)(NSDictionary *metadata); +/** + * Called upon a failure. + */ +@property (copy) void (^onFailure)(FSAudioStreamError error, NSString *errorDescription); + +/** + * Delegate. + */ +@property (nonatomic,unsafe_unretained) IBOutlet id delegate; + +@end + +/** + * To check the preloading status, use this delegate. + */ +@protocol FSAudioControllerDelegate + +@optional + +/** + * Called when the controller wants to start preloading an item. Return YES or NO + * depending if you want this item to be preloaded. + * + * @param audioController The audio controller which is doing the preloading. + * @param stream The stream which is wanted to be preloaded. + */ +- (BOOL)audioController:(FSAudioController *)audioController allowPreloadingForStream:(FSAudioStream *)stream; + +/** + * Called when the controller starts to preload an item. + * + * @param audioController The audio controller which is doing the preloading. + * @param stream The stream which is preloaded. + */ +- (void)audioController:(FSAudioController *)audioController preloadStartedForStream:(FSAudioStream *)stream; +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.m b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.m new file mode 100644 index 0000000..03fffa0 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioController.m @@ -0,0 +1,875 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import "FSAudioController.h" +#import "FSPlaylistItem.h" +#import "FSCheckContentTypeRequest.h" +#import "FSParsePlaylistRequest.h" +#import "FSParseRssPodcastFeedRequest.h" + +#import + +/** + * Private interface for FSAudioController. + */ +@interface FSAudioController () +- (void)notifyRetrievingURL; + +@property (readonly) FSAudioStream *audioStream; +@property (readonly) FSCheckContentTypeRequest *checkContentTypeRequest; +@property (readonly) FSParsePlaylistRequest *parsePlaylistRequest; +@property (readonly) FSParseRssPodcastFeedRequest *parseRssPodcastFeedRequest; +@property (nonatomic,assign) BOOL readyToPlay; +@property (nonatomic,assign) NSUInteger currentPlaylistItemIndex; +@property (nonatomic,strong) NSMutableArray *playlistItems; +@property (nonatomic,strong) NSMutableArray *streams; +@property (nonatomic,assign) BOOL needToSetVolume; +@property (nonatomic,assign) BOOL songSwitchInProgress; +@property (nonatomic,assign) float outputVolume; + +- (void)audioStreamStateDidChange:(NSNotification *)notification; +- (void)deactivateInactivateStreams:(NSUInteger)currentActiveStream; +- (void)setAudioSessionActive:(BOOL)active; + +@end + +/** + * Acts as a proxy object for FSAudioStream. Lazily initializes + * the stream when it is needed. + * + * A call to deactivate releases the stream. + */ +@interface FSAudioStreamProxy : NSObject { + FSAudioStream *_audioStream; +} + +@property (readonly) FSAudioStream *audioStream; +@property (nonatomic,copy) NSURL *url; +@property (nonatomic,weak) FSAudioController *audioController; + +- (void)deactivate; + +@end + +/* + * ======================================= + * FSAudioStreamProxy implementation. + * ======================================= + */ + +@implementation FSAudioStreamProxy + +- (id)init +{ + if (self = [super init]) { + } + return self; +} + +- (id)initWithAudioController:(FSAudioController *)controller +{ + if (self = [self init]) { + self.audioController = controller; + } + return self; +} + +- (void)dealloc +{ + if (self.audioController.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] FSAudioStreamProxy.dealloc: %@", __LINE__, self.url); + } + + [self deactivate]; +} + +- (FSAudioStream *)audioStream +{ + if (!_audioStream) { + FSStreamConfiguration *conf; + if (self.audioController.configuration) { + conf = self.audioController.configuration; + } else { + conf = [[FSStreamConfiguration alloc] init]; + } + + // Disable audio session handling for the audio stream; audio controller handles it + conf.automaticAudioSessionHandlingEnabled = NO; + + _audioStream = [[FSAudioStream alloc] initWithConfiguration:conf]; + + if (self.audioController.needToSetVolume) { + _audioStream.volume = self.audioController.outputVolume; + } + + if (self.url) { + _audioStream.url = self.url; + } + } + return _audioStream; +} + +- (void)deactivate +{ + [_audioStream stop]; + + _audioStream = nil; +} + +@end + +/* + * ======================================= + * FSAudioController implementation + * ======================================= + */ + +@implementation FSAudioController + +-(id)init +{ + if (self = [super init]) { + _url = nil; + _checkContentTypeRequest = nil; + _parsePlaylistRequest = nil; + _readyToPlay = NO; + _playlistItems = [[NSMutableArray alloc] init]; + _streams = [[NSMutableArray alloc] init]; + self.preloadNextPlaylistItemAutomatically = YES; + self.enableDebugOutput = NO; + self.automaticAudioSessionHandlingEnabled = YES; + self.configuration = [[FSStreamConfiguration alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioStreamStateDidChange:) + name:FSAudioStreamStateChangeNotification + object:nil]; + } + return self; +} + +- (id)initWithUrl:(NSURL *)url +{ + if (self = [self init]) { + self.url = url; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [_checkContentTypeRequest cancel]; + [_parsePlaylistRequest cancel]; + [_parseRssPodcastFeedRequest cancel]; + + for (FSAudioStreamProxy *proxy in _streams) { + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] dealloc. Deactivating stream %@", __LINE__, proxy.url); + } + + [proxy deactivate]; + } + + [self setAudioSessionActive:NO]; +} + +- (void)audioStreamStateDidChange:(NSNotification *)notification +{ + if (notification.object == self) { + // URL retrieving notification from ourselves, ignore + return; + } + + if (!(notification.object == self.audioStream)) { + // This doesn't concern us, return + return; + } + + NSDictionary *dict = [notification userInfo]; + int state = [[dict valueForKey:FSAudioStreamNotificationKey_State] intValue]; + + if (state == kFSAudioStreamEndOfFile) { + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] EOF reached for %@", __LINE__, self.audioStream.url); + } + + if (!self.preloadNextPlaylistItemAutomatically) { + // No preloading wanted, skip + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] Preloading disabled, return.", __LINE__); + } + + return; + } + + // Reached EOF for this stream, do we have another item waiting in the playlist? + if ([self hasNextItem]) { + FSAudioStreamProxy *proxy = [_streams objectAtIndex:self.currentPlaylistItemIndex + 1]; + FSAudioStream *nextStream = proxy.audioStream; + + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] Preloading %@", __LINE__, nextStream.url); + } + + if ([self.delegate respondsToSelector:@selector(audioController:allowPreloadingForStream:)]) { + if ([self.delegate audioController:self allowPreloadingForStream:nextStream]) { + [nextStream preload]; + } else { + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] Preloading disallowed for stream %@", __LINE__, nextStream.url); + } + } + } else { + // Start preloading the next stream; we can load this as there is no override + [nextStream preload]; + } + + if ([self.delegate respondsToSelector:@selector(audioController:preloadStartedForStream:)]) { + [self.delegate audioController:self preloadStartedForStream:nextStream]; + } + } + } else if (state == kFsAudioStreamStopped && !self.songSwitchInProgress) { + if (self.enableDebugOutput) { + NSLog(@"Stream %@ stopped. No next playlist items. Deactivating audio session", self.audioStream.url); + } + + [self setAudioSessionActive:NO]; + } else if (state == kFsAudioStreamPlaybackCompleted && [self hasNextItem]) { + self.currentPlaylistItemIndex = self.currentPlaylistItemIndex + 1; + self.songSwitchInProgress = YES; + + [self play]; + } else if (state == kFsAudioStreamFailed) { + if (self.enableDebugOutput) { + NSLog(@"Stream %@ failed. Deactivating audio session", self.audioStream.url); + } + + [self setAudioSessionActive:NO]; + } else if (state == kFsAudioStreamBuffering) { + if (self.enableDebugOutput) { + NSLog(@"Stream buffering. Activating audio session"); + } + + self.songSwitchInProgress = NO; + + if (self.automaticAudioSessionHandlingEnabled) { +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; +#endif + } + [self setAudioSessionActive:YES]; + } else if (state == kFsAudioStreamPlaying) { + self.currentPlaylistItem.audioDataByteCount = self.activeStream.audioDataByteCount; + } +} + +- (void)deactivateInactivateStreams:(NSUInteger)currentActiveStream +{ + NSUInteger streamIndex = 0; + + for (FSAudioStreamProxy *proxy in _streams) { + if (streamIndex != currentActiveStream) { + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] Deactivating stream %@", __LINE__, proxy.url); + } + + [proxy deactivate]; + } + streamIndex++; + } +} + +- (void)setAudioSessionActive:(BOOL)active +{ + if (self.automaticAudioSessionHandlingEnabled) { +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + [[AVAudioSession sharedInstance] setActive:active withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; +#else +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + [[AVAudioSession sharedInstance] setActive:active error:nil]; +#endif +#endif + } +} + +/* + * ======================================= + * Properties + * ======================================= + */ + +- (FSAudioStream *)audioStream +{ + FSAudioStream *stream = nil; + + if ([_streams count] == 0) { + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] Stream count %lu, creating a proxy object", __LINE__, (unsigned long)[_streams count]); + } + + FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self]; + [_streams addObject:proxy]; + } + + FSAudioStreamProxy *proxy = [_streams objectAtIndex:self.currentPlaylistItemIndex]; + + stream = proxy.audioStream; + + return stream; +} + +- (FSCheckContentTypeRequest *)checkContentTypeRequest +{ + if (!_checkContentTypeRequest) { + __weak FSAudioController *weakSelf = self; + + _checkContentTypeRequest = [[FSCheckContentTypeRequest alloc] init]; + _checkContentTypeRequest.url = self.url; + _checkContentTypeRequest.onCompletion = ^() { + if (weakSelf.checkContentTypeRequest.playlist) { + // The URL is a playlist; retrieve the contents + [weakSelf.parsePlaylistRequest start]; + } else if (weakSelf.checkContentTypeRequest.xml) { + // The URL may be an RSS feed, check the contents + [weakSelf.parseRssPodcastFeedRequest start]; + } else { + // Not a playlist; try directly playing the URL + + weakSelf.readyToPlay = YES; + [weakSelf play]; + } + }; + _checkContentTypeRequest.onFailure = ^() { + // Failed to check the format; try playing anyway + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioController: Failed to check the format, trying to play anyway, URL: %@", weakSelf.audioStream.url); +#endif + + weakSelf.readyToPlay = YES; + [weakSelf play]; + }; + } + return _checkContentTypeRequest; +} + +- (FSParsePlaylistRequest *)parsePlaylistRequest +{ + if (!_parsePlaylistRequest) { + __weak FSAudioController *weakSelf = self; + + _parsePlaylistRequest = [[FSParsePlaylistRequest alloc] init]; + _parsePlaylistRequest.onCompletion = ^() { + [weakSelf playFromPlaylist:weakSelf.parsePlaylistRequest.playlistItems]; + }; + _parsePlaylistRequest.onFailure = ^() { + // Failed to parse the playlist; try playing anyway + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioController: Playlist parsing failed, trying to play anyway, URL: %@", weakSelf.audioStream.url); +#endif + + weakSelf.readyToPlay = YES; + [weakSelf play]; + }; + } + return _parsePlaylistRequest; +} + +- (FSParseRssPodcastFeedRequest *)parseRssPodcastFeedRequest +{ + if (!_parseRssPodcastFeedRequest) { + __weak FSAudioController *weakSelf = self; + + _parseRssPodcastFeedRequest = [[FSParseRssPodcastFeedRequest alloc] init]; + _parseRssPodcastFeedRequest.onCompletion = ^() { + [weakSelf playFromPlaylist:weakSelf.parseRssPodcastFeedRequest.playlistItems]; + }; + _parseRssPodcastFeedRequest.onFailure = ^() { + // Failed to parse the XML file; try playing anyway + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioController: Failed to parse the RSS feed, trying to play anyway, URL: %@", weakSelf.audioStream.url); +#endif + + weakSelf.readyToPlay = YES; + [weakSelf play]; + }; + } + return _parseRssPodcastFeedRequest; +} + +- (void)notifyRetrievingURL +{ + if (self.onStateChange) { + self.onStateChange(kFsAudioStreamRetrievingURL); + } +} + +- (BOOL)isPlaying +{ + return [self.audioStream isPlaying]; +} + +/* + * ======================================= + * Public interface + * ======================================= + */ + +- (void)play +{ + if (!self.readyToPlay) { + /* + * Not ready to play; start by checking the content type of the given + * URL. + */ + [self.checkContentTypeRequest start]; + + NSDictionary *userInfo = @{FSAudioStreamNotificationKey_State: @(kFsAudioStreamRetrievingURL)}; + NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamStateChangeNotification object:self userInfo:userInfo]; + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + [NSTimer scheduledTimerWithTimeInterval:0 + target:self + selector:@selector(notifyRetrievingURL) + userInfo:nil + repeats:NO]; + + return; + } + + if ([self.playlistItems count] > 0) { + if (self.currentPlaylistItem.originatingUrl) { + self.audioStream.url = self.currentPlaylistItem.originatingUrl; + } else { + self.audioStream.url = self.currentPlaylistItem.url; + } + } else { + self.audioStream.url = self.url; + } + + if (self.onStateChange) { + self.audioStream.onStateChange = self.onStateChange; + } + if (self.onMetaDataAvailable) { + self.audioStream.onMetaDataAvailable = self.onMetaDataAvailable; + } + if (self.onFailure) { + self.audioStream.onFailure = self.onFailure; + } + + FSAudioStream *stream = self.audioStream; + + if (self.enableDebugOutput) { + NSLog(@"Playing %@", stream); + } + + [stream play]; +} + +- (void)playFromURL:(NSURL*)url +{ + if (!url) { + return; + } + + [_playlistItems removeAllObjects]; + + [self stop]; + + self.url = url; + + [self play]; +} + +- (void)playFromPlaylist:(NSArray *)playlist +{ + [self playFromPlaylist:playlist itemIndex:0]; +} + +- (void)playFromPlaylist:(NSArray *)playlist itemIndex:(NSUInteger)index +{ + [self stop]; + + self.playlistItems = [[NSMutableArray alloc] init]; + _streams = [[NSMutableArray alloc] init]; + + self.currentPlaylistItemIndex = 0; + + [self.playlistItems addObjectsFromArray:playlist]; + + for (FSPlaylistItem *item in playlist) { + FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self]; + proxy.url = item.url; + + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] playFromPlaylist. Adding stream proxy for %@", __LINE__, proxy.url); + } + + [_streams addObject:proxy]; + } + + [self playItemAtIndex:index]; +} + +- (void)playItemAtIndex:(NSUInteger)index +{ + NSUInteger count = [self countOfItems]; + + if (count == 0) { + return; + } + + if (index >= count) { + return; + } + + [self.audioStream stop]; + + self.currentPlaylistItemIndex = index; + + self.readyToPlay = YES; + + [self deactivateInactivateStreams:index]; + + [self play]; +} + +- (NSUInteger)countOfItems +{ + return [self.playlistItems count]; +} + +- (void)addItem:(FSPlaylistItem *)item +{ + if (!item) { + return; + } + + [self.playlistItems addObject:item]; + + FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self]; + proxy.url = item.url; + + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] addItem. Adding stream proxy for %@", __LINE__, proxy.url); + } + + [_streams addObject:proxy]; +} + +- (void)insertItem:(FSPlaylistItem *)item atIndex:(NSInteger)index +{ + if (!item) { + return; + } + + if (index > self.playlistItems.count) { + return; + } + + if(self.playlistItems.count == 0 && index == 0) { + [self addItem:item]; + return; + } + + [self.playlistItems insertObject:item + atIndex:index]; + + FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self]; + proxy.url = item.url; + + [_streams insertObject:proxy + atIndex:index]; + + if(index <= self.currentPlaylistItemIndex) { + _currentPlaylistItemIndex++; + } +} + +- (void)replaceItemAtIndex:(NSUInteger)index withItem:(FSPlaylistItem *)item +{ + NSUInteger count = [self countOfItems]; + + if (count == 0) { + return; + } + + if (index >= count) { + return; + } + + if (self.currentPlaylistItemIndex == index) { + // If the item is currently playing, do not allow the replacement + return; + } + + [self.playlistItems replaceObjectAtIndex:index withObject:item]; + + FSAudioStreamProxy *proxy = [[FSAudioStreamProxy alloc] initWithAudioController:self]; + proxy.url = item.url; + + [_streams replaceObjectAtIndex:index withObject:proxy]; +} + +- (void)moveItemAtIndex:(NSUInteger)from toIndex:(NSUInteger)to { + NSUInteger count = [self countOfItems]; + + if (count == 0) { + return; + } + + if (from >= count || to >= count) { + return; + } + + if(from == self.currentPlaylistItemIndex) { + _currentPlaylistItemIndex = to; + } + else if(from < self.currentPlaylistItemIndex && to > self.currentPlaylistItemIndex) { + _currentPlaylistItemIndex--; + } + else if(from > self.currentPlaylistItemIndex && to <= self.currentPlaylistItemIndex) { + _currentPlaylistItemIndex++; + } + + id object = [self.playlistItems objectAtIndex:from]; + [self.playlistItems removeObjectAtIndex:from]; + [self.playlistItems insertObject:object atIndex:to]; + + id obj = [_streams objectAtIndex:from]; + [_streams removeObjectAtIndex:from]; + [_streams insertObject:obj atIndex:to]; +} + +- (void)removeItemAtIndex:(NSUInteger)index +{ + NSUInteger count = [self countOfItems]; + + if (count == 0) { + return; + } + + if (index >= count) { + return; + } + + if (self.currentPlaylistItemIndex == index && self.isPlaying) { + // If the item is currently playing, do not allow the removal + return; + } + + FSPlaylistItem *current = self.currentPlaylistItem; + + [self.playlistItems removeObjectAtIndex:index]; + + if (self.enableDebugOutput) { + FSAudioStreamProxy *proxy = [_streams objectAtIndex:index]; + NSLog(@"[FSAudioController.m:%i] removeItemAtIndex. Removing stream proxy %@", __LINE__, proxy.url); + } + + [_streams removeObjectAtIndex:index]; + + // Update the current playlist item to be correct after the removal + NSUInteger itemIndex = 0; + for (FSPlaylistItem *item in self.playlistItems) { + if (item == current) { + self.currentPlaylistItemIndex = itemIndex; + + break; + } + + itemIndex++; + } +} + +- (void)stop +{ + if ([_streams count] > 0) { + // Avoid creating an instance if we don't have it + [self.audioStream stop]; + } + + [_checkContentTypeRequest cancel]; + [_parsePlaylistRequest cancel]; + [_parseRssPodcastFeedRequest cancel]; + + self.readyToPlay = NO; +} + +- (void)pause +{ + [self.audioStream pause]; +} + +-(BOOL)hasMultiplePlaylistItems +{ + return ([self.playlistItems count] > 1); +} + +-(BOOL)hasNextItem +{ + return [self hasMultiplePlaylistItems] && (self.currentPlaylistItemIndex + 1 < [self.playlistItems count]); +} + +-(BOOL)hasPreviousItem +{ + return ([self hasMultiplePlaylistItems] && (self.currentPlaylistItemIndex != 0)); +} + +-(void)playNextItem +{ + if ([self hasNextItem]) { + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] playNexItem. Stopping stream %@", __LINE__, self.audioStream.url); + } + [self.audioStream stop]; + + [self deactivateInactivateStreams:self.currentPlaylistItemIndex]; + + self.currentPlaylistItemIndex = self.currentPlaylistItemIndex + 1; + + [self play]; + } +} + +-(void)playPreviousItem +{ + if ([self hasPreviousItem]) { + if (self.enableDebugOutput) { + NSLog(@"[FSAudioController.m:%i] playPreviousItem. Stopping stream %@", __LINE__, self.audioStream.url); + } + [self.audioStream stop]; + + [self deactivateInactivateStreams:self.currentPlaylistItemIndex]; + + self.currentPlaylistItemIndex = self.currentPlaylistItemIndex - 1; + + [self play]; + } +} + +/* + * ======================================= + * Properties + * ======================================= + */ + +- (void)setVolume:(float)volume +{ + self.outputVolume = volume; + self.needToSetVolume = YES; + + if ([_streams count] > 0) { + self.audioStream.volume = self.outputVolume; + } +} + +- (float)volume +{ + return self.outputVolume; +} + +- (void)setUrl:(NSURL *)url +{ + [self stop]; + + if (url) { + NSURL *copyOfURL = [url copy]; + _url = copyOfURL; + + self.checkContentTypeRequest.url = _url; + self.parsePlaylistRequest.url = _url; + self.parseRssPodcastFeedRequest.url = _url; + + if ([_url isFileURL]) { + /* + * Local file URLs can be directly played + */ + self.readyToPlay = YES; + } + } else { + _url = nil; + } +} + +- (NSURL* )url +{ + if (!_url) { + return nil; + } + + NSURL *copyOfURL = [_url copy]; + return copyOfURL; +} + +- (FSAudioStream *)activeStream +{ + if ([_streams count] > 0) { + return self.audioStream; + } + return nil; +} + +- (FSPlaylistItem *)currentPlaylistItem +{ + if (self.readyToPlay) { + if ([self.playlistItems count] > 0) { + FSPlaylistItem *playlistItem = (self.playlistItems)[self.currentPlaylistItemIndex]; + return playlistItem; + } + } + return nil; +} + +- (void (^)(FSAudioStreamState state))onStateChange +{ + return _onStateChangeBlock; +} + +- (void (^)(NSDictionary *metaData))onMetaDataAvailable +{ + return _onMetaDataAvailableBlock; +} + +- (void (^)(FSAudioStreamError error, NSString *errorDescription))onFailure +{ + return _onFailureBlock; +} + +- (void)setOnStateChange:(void (^)(FSAudioStreamState))newOnStateValue +{ + _onStateChangeBlock = newOnStateValue; + + if ([_streams count] > 0) { + self.audioStream.onStateChange = _onStateChangeBlock; + } +} + +- (void)setOnMetaDataAvailable:(void (^)(NSDictionary *))newOnMetaDataAvailableValue +{ + _onMetaDataAvailableBlock = newOnMetaDataAvailableValue; + + if ([_streams count] > 0) { + self.audioStream.onMetaDataAvailable = _onMetaDataAvailableBlock; + } +} + +- (void)setOnFailure:(void (^)(FSAudioStreamError error, NSString *errorDescription))newOnFailureValue +{ + _onFailureBlock = newOnFailureValue; + + if ([_streams count] > 0) { + self.audioStream.onFailure = _onFailureBlock; + } +} + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.h new file mode 100644 index 0000000..7236002 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.h @@ -0,0 +1,600 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import +#import + +/** + * The major version of the current release. + */ +#define FREESTREAMER_VERSION_MAJOR 4 + +/** + * The minor version of the current release. + */ +#define FREESTREAMER_VERSION_MINOR 0 + +/** + * The reversion of the current release + */ +#define FREESTREAMER_VERSION_REVISION 0 + +/** + * Follow this notification for the audio stream state changes. + */ +extern NSString* const FSAudioStreamStateChangeNotification; +extern NSString* const FSAudioStreamNotificationKey_State; + +/** + * Follow this notification for the audio stream errors. + */ +extern NSString* const FSAudioStreamErrorNotification; +extern NSString* const FSAudioStreamNotificationKey_Error; + +/** + * Follow this notification for the audio stream metadata. + */ +extern NSString* const FSAudioStreamMetaDataNotification; +extern NSString* const FSAudioStreamNotificationKey_MetaData; + +/** + * The audio stream state. + */ +typedef NS_ENUM(NSInteger, FSAudioStreamState) { + /** + * Retrieving URL. + */ + kFsAudioStreamRetrievingURL, + /** + * Stopped. + */ + kFsAudioStreamStopped, + /** + * Buffering. + */ + kFsAudioStreamBuffering, + /** + * Playing. + */ + kFsAudioStreamPlaying, + /** + * Paused. + */ + kFsAudioStreamPaused, + /** + * Seeking. + */ + kFsAudioStreamSeeking, + /** + * The stream has received all the data for a file. + */ + kFSAudioStreamEndOfFile, + /** + * Failed. + */ + kFsAudioStreamFailed, + /** + * Started retrying. + */ + kFsAudioStreamRetryingStarted, + /** + * Retrying succeeded. + */ + kFsAudioStreamRetryingSucceeded, + /** + * Retrying failed. + */ + kFsAudioStreamRetryingFailed, + /** + * Playback completed. + */ + kFsAudioStreamPlaybackCompleted, + /** + * Unknown state. + */ + kFsAudioStreamUnknownState +}; + +/** + * The audio stream errors. + */ +typedef NS_ENUM(NSInteger, FSAudioStreamError) { + /** + * No error. + */ + kFsAudioStreamErrorNone = 0, + /** + * Error opening the stream. + */ + kFsAudioStreamErrorOpen = 1, + /** + * Error parsing the stream. + */ + kFsAudioStreamErrorStreamParse = 2, + /** + * Network error. + */ + kFsAudioStreamErrorNetwork = 3, + /** + * Unsupported format. + */ + kFsAudioStreamErrorUnsupportedFormat = 4, + /** + * Stream buffered too often. + */ + kFsAudioStreamErrorStreamBouncing = 5, + /** + * Stream playback was terminated by the operating system. + */ + kFsAudioStreamErrorTerminated = 6 +}; + +@protocol FSPCMAudioStreamDelegate; +@class FSAudioStreamPrivate; + +/** + * The audio stream playback position. + */ +typedef struct { + unsigned minute; + unsigned second; + + /** + * Playback time in seconds. + */ + float playbackTimeInSeconds; + + /** + * Position within the stream, where 0 is the beginning + * and 1.0 is the end. + */ + float position; +} FSStreamPosition; + +/** + * The audio stream seek byte offset. + */ +typedef struct { + UInt64 start; + UInt64 end; + /** + * Position within the stream, where 0 is the beginning + * and 1.0 is the end. + */ + float position; +} FSSeekByteOffset; + +/** + * Audio levels. + */ +typedef struct { + Float32 averagePower; + Float32 peakPower; +} FSLevelMeterState; + +/** + * The low-level stream configuration. + */ +@interface FSStreamConfiguration : NSObject { +} + +/** + * The number of buffers. + */ +@property (nonatomic,assign) unsigned bufferCount; +/** + * The size of each buffer. + */ +@property (nonatomic,assign) unsigned bufferSize; +/** + * The number of packet descriptions. + */ +@property (nonatomic,assign) unsigned maxPacketDescs; +/** + * The HTTP connection buffer size. + */ +@property (nonatomic,assign) unsigned httpConnectionBufferSize; +/** + * The output sample rate. + */ +@property (nonatomic,assign) double outputSampleRate; +/** + * The number of output channels. + */ +@property (nonatomic,assign) long outputNumChannels; +/** + * The interval within the stream may enter to the buffering state before it fails. + */ +@property (nonatomic,assign) int bounceInterval; +/** + * The number of times the stream may enter the buffering state before it fails. + */ +@property (nonatomic,assign) int maxBounceCount; +/** + * The stream must start within this seconds before it fails. + */ +@property (nonatomic,assign) int startupWatchdogPeriod; +/** + * Allow buffering of this many bytes before the cache is full. + */ +@property (nonatomic,assign) int maxPrebufferedByteCount; +/** + * Calculate prebuffer sizes dynamically using the stream bitrate in seconds instead of bytes. + */ +@property (nonatomic,assign) BOOL usePrebufferSizeCalculationInSeconds; +/** + * Calculate prebuffer sizes using the packet counts. + */ +@property (nonatomic,assign) BOOL usePrebufferSizeCalculationInPackets; +/** + * Require buffering of this many bytes before the playback can start for a continuous stream. + */ +@property (nonatomic,assign) float requiredPrebufferSizeInSeconds; +/** + * Require buffering of this many bytes before the playback can start for a continuous stream. + */ +@property (nonatomic,assign) int requiredInitialPrebufferedByteCountForContinuousStream; +/** + * Require buffering of this many bytes before the playback can start a non-continuous stream. + */ +@property (nonatomic,assign) int requiredInitialPrebufferedByteCountForNonContinuousStream; +/** + * Require buffering of this many packets before the playback can start. + */ +@property (nonatomic,assign) int requiredInitialPrebufferedPacketCount; +/** + * The HTTP user agent used for stream operations. + */ +@property (nonatomic,strong) NSString *userAgent; +/** + * The directory used for caching the streamed files. + */ +@property (nonatomic,strong) NSString *cacheDirectory; +/** + * The HTTP headers that are appended to the request when the streaming starts. Notice + * that the headers override any headers previously set by FreeStreamer. + */ +@property (nonatomic,strong) NSDictionary *predefinedHttpHeaderValues; +/** + * The property determining if caching the streams to the disk is enabled. + */ +@property (nonatomic,assign) BOOL cacheEnabled; +/** + * The property determining if seeking from the audio packets stored in cache is enabled. + * The benefit is that seeking is faster in the case the audio packets are already cached in memory. + */ +@property (nonatomic,assign) BOOL seekingFromCacheEnabled; +/** + * The property determining if FreeStreamer should handle audio session automatically. + * Leave it on if you don't want to handle the audio session by yourself. + */ +@property (nonatomic,assign) BOOL automaticAudioSessionHandlingEnabled; +/** + * The property enables time and pitch conversion for the audio queue. Put it on + * if you want to use the play rate setting. + */ +@property (nonatomic,assign) BOOL enableTimeAndPitchConversion; +/** + * Requires the content type given by the server to match an audio content type. + */ +@property (nonatomic,assign) BOOL requireStrictContentTypeChecking; +/** + * The maximum size of the disk cache in bytes. + */ +@property (nonatomic,assign) int maxDiskCacheSize; + +@end + +/** + * Statistics on the stream state. + */ +@interface FSStreamStatistics : NSObject { +} + +/** + * Time when the statistics were gathered. + */ +@property (nonatomic,strong) NSDate *snapshotTime; +/** + * Time in a pretty format. + */ +@property (nonatomic,readonly) NSString *snapshotTimeFormatted; +/** + * Audio stream packet count. + */ +@property (nonatomic,assign) NSUInteger audioStreamPacketCount; +/** + * Audio queue used buffers count. + */ +@property (nonatomic,assign) NSUInteger audioQueueUsedBufferCount; +/** + * Audio stream PCM packet queue count. + */ +@property (nonatomic,assign) NSUInteger audioQueuePCMPacketQueueCount; + +@end + +NSString* freeStreamerReleaseVersion(void); + +/** + * FSAudioStream is a class for streaming audio files from an URL. + * It must be directly fed with an URL, which contains audio. That is, + * playlists or other non-audio formats yield an error. + * + * To start playback, the stream must be either initialized with an URL + * or the playback URL can be set with the url property. The playback + * is started with the play method. It is possible to pause or stop + * the stream with the respective methods. + * + * Non-continuous streams (audio streams with a known duration) can be + * seeked with the seekToPosition method. + * + * Note that FSAudioStream is not designed to be thread-safe! That means + * that using the streamer from multiple threads without syncronization + * could cause problems. It is recommended to keep the streamer in the + * main thread and call the streamer methods only from the main thread + * (consider using performSelectorOnMainThread: if calls from multiple + * threads are needed). + */ +@interface FSAudioStream : NSObject { + FSAudioStreamPrivate *_private; +} + +/** + * Initializes the audio stream with an URL. + * + * @param url The URL from which the stream data is retrieved. + */ +- (id)initWithUrl:(NSURL *)url; + +/** + * Initializes the stream with a configuration. + * + * @param configuration The stream configuration. + */ +- (id)initWithConfiguration:(FSStreamConfiguration *)configuration; + +/** + * Starts preload the stream. If no preload URL is + * defined, an error will occur. + */ +- (void)preload; + +/** + * Starts playing the stream. If no playback URL is + * defined, an error will occur. + */ +- (void)play; + +/** + * Starts playing the stream from the given URL. + * + * @param url The URL from which the stream data is retrieved. + */ +- (void)playFromURL:(NSURL*)url; + +/** + * Starts playing the stream from the given offset. + * The offset can be retrieved from the stream with the + * currentSeekByteOffset property. + * + * @param offset The offset where to start playback from. + */ +- (void)playFromOffset:(FSSeekByteOffset)offset; + +/** + * Stops the stream playback. + */ +- (void)stop; + +/** + * If the stream is playing, the stream playback is paused upon calling pause. + * Otherwise (the stream is paused), calling pause will continue the playback. + */ +- (void)pause; + +/** + * Rewinds the stream. Only possible for continuous streams. + * + * @param seconds Seconds to rewind the stream. + */ +- (void)rewind:(unsigned)seconds; + +/** + * Seeks the stream to a given position. Requires a non-continuous stream + * (a stream with a known duration). + * + * @param position The stream position to seek to. + */ +- (void)seekToPosition:(FSStreamPosition)position; + +/** + * Sets the audio stream playback rate from 0.5 to 2.0. + * Value 1.0 means the normal playback rate. Values below + * 1.0 means a slower playback rate than usual and above + * 1.0 a faster playback rate. Notice that using a faster + * playback rate than 1.0 may mean that you have to increase + * the buffer sizes for the stream still to play. + * + * The play rate has only effect if the stream is playing. + * + * @param playRate The playback rate. + */ +- (void)setPlayRate:(float)playRate; + +/** + * Returns the playback status: YES if the stream is playing, NO otherwise. + */ +- (BOOL)isPlaying; + +/** + * Cleans all cached data from the persistent storage. + */ +- (void)expungeCache; + +/** + * The stream URL. + */ +@property (nonatomic,assign) NSURL *url; +/** + * Determines if strict content type checking is required. If the audio stream + * cannot determine that the stream is actually an audio stream, the stream + * does not play. Disabling strict content type checking bypasses the + * stream content type checks and tries to play the stream regardless + * of the content type information given by the server. + */ +@property (nonatomic,assign) BOOL strictContentTypeChecking; +/** + * Set an output file to store the stream contents to a file. + */ +@property (nonatomic,assign) NSURL *outputFile; +/** + * Sets a default content type for the stream. Used if + * the stream content type is not available. + */ +@property (nonatomic,assign) NSString *defaultContentType; +/** + * The property has the content type of the stream, for instance audio/mpeg. + */ +@property (nonatomic,readonly) NSString *contentType; +/** + * The property has the suggested file extension for the stream based on the stream content type. + */ +@property (nonatomic,readonly) NSString *suggestedFileExtension; +/** + * Sets a default content length for the stream. Used if + * the stream content-length is not available. + */ +@property (nonatomic, assign) UInt64 defaultContentLength; +/** + * The property has the content length of the stream (in bytes). The length is zero if + * the stream is continuous. + */ +@property (nonatomic,readonly) UInt64 contentLength; +/** + * The number of bytes of audio data. Notice that this may differ + * from the number of bytes the server returns for the content length! + * For instance audio file meta data is excluded from the count. + * Effectively you can use this property for seeking calculations. + */ +@property (nonatomic,readonly) UInt64 audioDataByteCount; +/** + * This property has the current playback position, if the stream is non-continuous. + * The current playback position cannot be determined for continuous streams. + */ +@property (nonatomic,readonly) FSStreamPosition currentTimePlayed; +/** + * This property has the duration of the stream, if the stream is non-continuous. + * Continuous streams do not have a duration. + */ +@property (nonatomic,readonly) FSStreamPosition duration; +/** + * This property has the current seek byte offset of the stream, if the stream is non-continuous. + * Continuous streams do not have a seek byte offset. + */ +@property (nonatomic,readonly) FSSeekByteOffset currentSeekByteOffset; +/** + * This property has the bit rate of the stream. The bit rate is initially 0, + * before the stream has processed enough packets to calculate the bit rate. + */ +@property (nonatomic,readonly) float bitRate; +/** + * The property is true if the stream is continuous (no known duration). + */ +@property (nonatomic,readonly) BOOL continuous; +/** + * The property is true if the stream has been cached locally. + */ +@property (nonatomic,readonly) BOOL cached; +/** + * This property has the number of bytes buffered for this stream. + */ +@property (nonatomic,readonly) size_t prebufferedByteCount; +/** + * This property holds the current playback volume of the stream, + * from 0.0 to 1.0. + * + * Note that the overall volume is still constrained by the volume + * set by the user! So the actual volume cannot be higher + * than the volume currently set by the user. For example, if + * requesting a volume of 0.5, then the volume will be 50% + * lower than the current playback volume set by the user. + */ +@property (nonatomic,assign) float volume; +/** + * The current size of the disk cache. + */ +@property (nonatomic,readonly) unsigned long long totalCachedObjectsSize; +/** + * The property determines the amount of times the stream has tried to retry the playback + * in case of failure. + */ +@property (nonatomic,readonly) NSUInteger retryCount; +/** + * Holds the maximum amount of playback retries that will be + * performed before entering kFsAudioStreamRetryingFailed state. + * Default is 3. + */ +@property (nonatomic,assign) NSUInteger maxRetryCount; +/** + * The property determines the current audio levels. + */ +@property (nonatomic,readonly) FSLevelMeterState levels; +/** + * This property holds the current statistics for the stream state. + */ +@property (nonatomic,readonly) FSStreamStatistics *statistics; +/** + * Called upon completion of the stream. Note that for continuous + * streams this is never called. + */ +@property (copy) void (^onCompletion)(void); +/** + * Called upon a state change. + */ +@property (copy) void (^onStateChange)(FSAudioStreamState state); +/** + * Called upon a meta data is available. + */ +@property (copy) void (^onMetaDataAvailable)(NSDictionary *metadata); +/** + * Called upon a failure. + */ +@property (copy) void (^onFailure)(FSAudioStreamError error, NSString *errorDescription); +/** + * The property has the low-level stream configuration. + */ +@property (readonly) FSStreamConfiguration *configuration; +/** + * Delegate. + */ +@property (nonatomic,unsafe_unretained) IBOutlet id delegate; + +@end + +/** + * To access the PCM audio data, use this delegate. + */ +@protocol FSPCMAudioStreamDelegate + +@optional +/** + * Called when there are PCM audio samples available. Do not do any blocking operations + * when you receive the data. Instead, copy the data and process it so that the + * main event loop doesn't block. Failing to do so may cause glitches to the audio playback. + * + * Notice that the delegate callback may occur from other than the main thread so make + * sure your delegate code is thread safe. + * + * @param audioStream The audio stream the samples are from. + * @param samples The samples as a buffer list. + * @param frames The number of frames. + * @param description Description of the data provided. + */ +- (void)audioStream:(FSAudioStream *)audioStream samplesAvailable:(AudioBufferList *)samples frames:(UInt32)frames description: (AudioStreamPacketDescription)description; +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.mm b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.mm new file mode 100644 index 0000000..caacf5c --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSAudioStream.mm @@ -0,0 +1,1905 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import "FSAudioStream.h" + +#import "Reachability.h" + +#include "audio_stream.h" +#include "stream_configuration.h" +#include "input_stream.h" + +#import + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) +#import +#import +#endif + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) +static NSMutableDictionary *fsAudioStreamPrivateActiveSessions = nil; +#endif + +@interface FSCacheObject : NSObject { +} + +@property (strong,nonatomic) NSString *path; +@property (strong,nonatomic) NSString *name; +@property (strong,nonatomic) NSDictionary *attributes; +@property (nonatomic,readonly) unsigned long long fileSize; +@property (nonatomic,readonly) NSDate *modificationDate; + +@end + +@implementation FSCacheObject + +- (unsigned long long)fileSize +{ + NSNumber *fileSizeNumber = [self.attributes objectForKey:NSFileSize]; + return [fileSizeNumber longLongValue]; +} + +- (NSDate *)modificationDate +{ + NSDate *date = [self.attributes objectForKey:NSFileModificationDate]; + return date; +} + +@end + +static NSInteger sortCacheObjects(id co1, id co2, void *keyForSorting) +{ + FSCacheObject *cached1 = (FSCacheObject *)co1; + FSCacheObject *cached2 = (FSCacheObject *)co2; + + NSDate *d1 = cached1.modificationDate; + NSDate *d2 = cached2.modificationDate; + + return [d1 compare:d2]; +} + +@implementation FSStreamConfiguration + +- (id)init +{ + self = [super init]; + if (self) { + NSMutableString *systemVersion = [[NSMutableString alloc] init]; + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + [systemVersion appendString:@"iOS "]; + [systemVersion appendString:[[UIDevice currentDevice] systemVersion]]; +#else + [systemVersion appendString:@"OS X"]; +#endif + + self.bufferCount = 64; + self.bufferSize = 8192; + self.maxPacketDescs = 512; + self.httpConnectionBufferSize = 8192; + self.outputSampleRate = 44100; + self.outputNumChannels = 2; + self.bounceInterval = 10; + self.maxBounceCount = 4; // Max number of bufferings in bounceInterval seconds + self.startupWatchdogPeriod = 30; // If the stream doesn't start to play in this seconds, the watchdog will fail it +#ifdef __LP64__ + /* Increase the max in-memory cache to 10 MB with newer 64 bit devices */ + self.maxPrebufferedByteCount = 10000000; // 10 MB +#else + self.maxPrebufferedByteCount = 1000000; // 1 MB +#endif + self.userAgent = [NSString stringWithFormat:@"FreeStreamer/%@ (%@)", freeStreamerReleaseVersion(), systemVersion]; + self.cacheEnabled = YES; + self.seekingFromCacheEnabled = YES; + self.automaticAudioSessionHandlingEnabled = YES; + self.enableTimeAndPitchConversion = NO; + self.requireStrictContentTypeChecking = YES; + self.maxDiskCacheSize = 256000000; // 256 MB + self.usePrebufferSizeCalculationInSeconds = YES; + self.usePrebufferSizeCalculationInPackets = NO; + self.requiredInitialPrebufferedPacketCount = 32; + self.requiredPrebufferSizeInSeconds = 7; + // With dynamic calculation, these are actually the maximum sizes, the dynamic + // calculation may lower the sizes based on the stream bitrate + self.requiredInitialPrebufferedByteCountForContinuousStream = 256000; + self.requiredInitialPrebufferedByteCountForNonContinuousStream = 256000; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + + if ([paths count] > 0) { + self.cacheDirectory = [paths objectAtIndex:0]; + } + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + AVAudioSession *session = [AVAudioSession sharedInstance]; + double sampleRate = session.sampleRate; + if (sampleRate > 0) { + self.outputSampleRate = sampleRate; + } + NSInteger channels = session.outputNumberOfChannels; + if (channels > 0) { + self.outputNumChannels = channels; + } +#endif + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + /* iOS */ + +#else + /* OS X */ + + self.requiredPrebufferSizeInSeconds = 3; + + // No need to be so concervative with the cache sizes + self.maxPrebufferedByteCount = 16000000; // 16 MB +#endif + } + + return self; +} + +@end + +static NSDateFormatter *statisticsDateFormatter = nil; + +@implementation FSStreamStatistics + +- (NSString *)snapshotTimeFormatted +{ + if (!statisticsDateFormatter) { + statisticsDateFormatter = [[NSDateFormatter alloc] init]; + [statisticsDateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; + } + return [statisticsDateFormatter stringFromDate:self.snapshotTime]; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"%@\t%lu\t%lu\t%lu", + self.snapshotTimeFormatted, + (unsigned long)self.audioStreamPacketCount, + (unsigned long)self.audioQueueUsedBufferCount, + (unsigned long)self.audioQueuePCMPacketQueueCount]; +} + +@end + +NSString *freeStreamerReleaseVersion() +{ + NSString *version = [NSString stringWithFormat:@"%i.%i.%i", + FREESTREAMER_VERSION_MAJOR, + FREESTREAMER_VERSION_MINOR, + FREESTREAMER_VERSION_REVISION]; + return version; +} + +NSString* const FSAudioStreamStateChangeNotification = @"FSAudioStreamStateChangeNotification"; +NSString* const FSAudioStreamNotificationKey_Stream = @"stream"; +NSString* const FSAudioStreamNotificationKey_State = @"state"; + +NSString* const FSAudioStreamErrorNotification = @"FSAudioStreamErrorNotification"; +NSString* const FSAudioStreamNotificationKey_Error = @"error"; +NSString* const FSAudioStreamNotificationKey_ErrorDescription = @"errorDescription"; + +NSString* const FSAudioStreamMetaDataNotification = @"FSAudioStreamMetaDataNotification"; +NSString* const FSAudioStreamNotificationKey_MetaData = @"metadata"; + +class AudioStreamStateObserver : public astreamer::Audio_Stream_Delegate +{ +public: + astreamer::Audio_Stream *source; + FSAudioStreamPrivate *priv; + + void audioStreamErrorOccurred(int errorCode, CFStringRef errorDescription); + void audioStreamStateChanged(astreamer::Audio_Stream::State state); + void audioStreamMetaDataAvailable(std::map metaData); + void samplesAvailable(AudioBufferList *samples, UInt32 frames, AudioStreamPacketDescription description); + void bitrateAvailable(); +}; + +/* + * =============================================================== + * FSAudioStream private implementation + * =============================================================== + */ + +@interface FSAudioStreamPrivate : NSObject { + astreamer::Audio_Stream *_audioStream; + NSURL *_url; + AudioStreamStateObserver *_observer; + NSString *_defaultContentType; + Reachability *_reachability; + FSSeekByteOffset _lastSeekByteOffset; + BOOL _wasPaused; +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + UIBackgroundTaskIdentifier _backgroundTask; +#endif +} + +@property (nonatomic,assign) NSURL *url; +@property (nonatomic,assign) BOOL strictContentTypeChecking; +@property (nonatomic,assign) NSString *defaultContentType; +@property (readonly) NSString *contentType; +@property (readonly) NSString *suggestedFileExtension; +@property (nonatomic, assign) UInt64 defaultContentLength; +@property (readonly) UInt64 contentLength; +@property (nonatomic,assign) NSURL *outputFile; +@property (nonatomic,assign) BOOL wasInterrupted; +@property (nonatomic,assign) BOOL wasDisconnected; +@property (nonatomic,assign) BOOL wasContinuousStream; +@property (nonatomic,assign) BOOL internetConnectionAvailable; +@property (nonatomic,assign) NSUInteger maxRetryCount; +@property (nonatomic,assign) NSUInteger retryCount; +@property (readonly) FSStreamStatistics *statistics; +@property (readonly) FSLevelMeterState levels; +@property (readonly) size_t prebufferedByteCount; +@property (readonly) FSSeekByteOffset currentSeekByteOffset; +@property (readonly) float bitRate; +@property (readonly) FSStreamConfiguration *configuration; +@property (readonly) NSString *formatDescription; +@property (readonly) BOOL cached; +@property (copy) void (^onCompletion)(); +@property (copy) void (^onStateChange)(FSAudioStreamState state); +@property (copy) void (^onMetaDataAvailable)(NSDictionary *metaData); +@property (copy) void (^onFailure)(FSAudioStreamError error, NSString *errorDescription); +@property (nonatomic,unsafe_unretained) id delegate; +@property (nonatomic,unsafe_unretained) FSAudioStream *stream; + +- (AudioStreamStateObserver *)streamStateObserver; + +- (void)endBackgroundTask; + +- (void)reachabilityChanged:(NSNotification *)note; +- (void)interruptionOccurred:(NSNotification *)notification; + +- (void)notifyPlaybackStopped; +- (void)notifyPlaybackBuffering; +- (void)notifyPlaybackPlaying; +- (void)notifyPlaybackPaused; +- (void)notifyPlaybackSeeking; +- (void)notifyPlaybackEndOfFile; +- (void)notifyPlaybackFailed; +- (void)notifyPlaybackCompletion; +- (void)notifyPlaybackUnknownState; +- (void)notifyRetryingStarted; +- (void)notifyRetryingSucceeded; +- (void)notifyRetryingFailed; +- (void)notifyStateChange:(FSAudioStreamState)streamerState; + +- (void)attemptRestart; +- (void)expungeCache; +- (void)play; +- (void)playFromURL:(NSURL*)url; +- (void)playFromOffset:(FSSeekByteOffset)offset; +- (void)stop; +- (BOOL)isPlaying; +- (void)pause; +- (void)rewind:(unsigned)seconds; +- (void)seekToOffset:(float)offset; +- (float)currentVolume; +- (unsigned long long)totalCachedObjectsSize; +- (void)setVolume:(float)volume; +- (void)setPlayRate:(float)playRate; +- (astreamer::AS_Playback_Position)playbackPosition; +- (UInt64)audioDataByteCount; +- (float)durationInSeconds; +- (void)bitrateAvailable; +@end + +@implementation FSAudioStreamPrivate + +-(id)init +{ + NSAssert([NSThread isMainThread], @"FSAudioStreamPrivate.init needs to be called in the main thread"); + + if (self = [super init]) { + _url = nil; + + _observer = new AudioStreamStateObserver(); + _observer->priv = self; + + _audioStream = new astreamer::Audio_Stream(); + _observer->source = _audioStream; + + _audioStream->m_delegate = _observer; + + _reachability = nil; + + _delegate = nil; + + _maxRetryCount = 3; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reachabilityChanged:) + name:kReachabilityChangedNotification + object:nil]; + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + _backgroundTask = UIBackgroundTaskInvalid; + + @synchronized (self) { + if (!fsAudioStreamPrivateActiveSessions) { + fsAudioStreamPrivateActiveSessions = [[NSMutableDictionary alloc] init]; + } + } + + if (self.configuration.automaticAudioSessionHandlingEnabled) { + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; + } +#endif + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(interruptionOccurred:) + name:AVAudioSessionInterruptionNotification + object:nil]; +#endif + } + return self; +} + +- (void)dealloc +{ + NSAssert([NSThread isMainThread], @"FSAudioStreamPrivate.dealloc needs to be called in the main thread"); + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self stop]; + + _delegate = nil; + + delete _audioStream; + _audioStream = nil; + delete _observer; + _observer = nil; + + // Clean up the disk cache. + + if (!self.configuration.cacheEnabled) { + // Don't clean up if cache not enabled + return; + } + + unsigned long long totalCacheSize = 0; + + NSMutableArray *cachedFiles = [[NSMutableArray alloc] init]; + + for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.configuration.cacheDirectory error:nil]) { + if ([file hasPrefix:@"FSCache-"]) { + FSCacheObject *cacheObj = [[FSCacheObject alloc] init]; + cacheObj.name = file; + cacheObj.path = [NSString stringWithFormat:@"%@/%@", self.configuration.cacheDirectory, cacheObj.name]; + cacheObj.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:cacheObj.path error:nil]; + + totalCacheSize += [cacheObj fileSize]; + + if (![cacheObj.name hasSuffix:@".metadata"]) { + [cachedFiles addObject:cacheObj]; + } + } + } + + // Sort by the modification date. + // In this way the older content will be removed first from the cache. + [cachedFiles sortUsingFunction:sortCacheObjects context:NULL]; + + for (FSCacheObject *cacheObj in cachedFiles) { + if (totalCacheSize < self.configuration.maxDiskCacheSize) { + break; + } + + FSCacheObject *cachedMetaData = [[FSCacheObject alloc] init]; + cachedMetaData.name = [NSString stringWithFormat:@"%@.metadata", cacheObj.name]; + cachedMetaData.path = [NSString stringWithFormat:@"%@/%@", self.configuration.cacheDirectory, cachedMetaData.name]; + cachedMetaData.attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:cachedMetaData.path error:nil]; + + if (![[NSFileManager defaultManager] removeItemAtPath:cachedMetaData.path error:nil]) { + continue; + } + totalCacheSize -= [cachedMetaData fileSize]; + + if (![[NSFileManager defaultManager] removeItemAtPath:cacheObj.path error:nil]) { + continue; + } + totalCacheSize -= [cacheObj fileSize]; + } + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + @synchronized (self) { + [fsAudioStreamPrivateActiveSessions removeObjectForKey:[NSNumber numberWithUnsignedLong:(unsigned long)self]]; + + if ([fsAudioStreamPrivateActiveSessions count] == 0) { + if (self.configuration.automaticAudioSessionHandlingEnabled) { +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; +#else + [[AVAudioSession sharedInstance] setActive:NO error:nil]; +#endif + } + } + } +#endif +} + +- (void)endBackgroundTask +{ +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + if (_backgroundTask != UIBackgroundTaskInvalid) { + [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask]; + _backgroundTask = UIBackgroundTaskInvalid; + } +#endif +} + +- (AudioStreamStateObserver *)streamStateObserver +{ + return _observer; +} + +- (void)setUrl:(NSURL *)url +{ + if ([self isPlaying]) { + [self stop]; + } + + @synchronized (self) { + if ([url isEqual:_url]) { + return; + } + + _url = [url copy]; + + _audioStream->setUrl((__bridge CFURLRef)_url); + } + + if ([self isPlaying]) { + [self play]; + } +} + +- (NSURL*)url +{ + if (!_url) { + return nil; + } + + NSURL *copyOfURL = [_url copy]; + return copyOfURL; +} + +- (void)setStrictContentTypeChecking:(BOOL)strictContentTypeChecking +{ + _audioStream->setStrictContentTypeChecking(strictContentTypeChecking); +} + +- (BOOL)strictContentTypeChecking +{ + return _audioStream->strictContentTypeChecking(); +} + +- (void)playFromURL:(NSURL*)url +{ + [self setUrl:url]; + [self play]; +} + +- (void)playFromOffset:(FSSeekByteOffset)offset +{ + _wasPaused = NO; + + if (_audioStream->isPreloading()) { + _audioStream->seekToOffset(offset.position); + _audioStream->setPreloading(false); + } else { + astreamer::Input_Stream_Position position; + position.start = offset.start; + position.end = offset.end; + + _audioStream->open(&position); + + _audioStream->setSeekOffset(offset.position); + _audioStream->setContentLength(offset.end); + } + + if (!_reachability) { + _reachability = [Reachability reachabilityForInternetConnection]; + + [_reachability startNotifier]; + } +} + +- (void)setDefaultContentType:(NSString *)defaultContentType +{ + if (defaultContentType) { + _defaultContentType = [defaultContentType copy]; + _audioStream->setDefaultContentType((__bridge CFStringRef)_defaultContentType); + } else { + _audioStream->setDefaultContentType(NULL); + } +} + +- (NSString*)defaultContentType +{ + if (!_defaultContentType) { + return nil; + } + + NSString *copyOfDefaultContentType = [_defaultContentType copy]; + return copyOfDefaultContentType; +} + +- (NSString*)contentType +{ + CFStringRef c = _audioStream->contentType(); + if (c) { + return CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, c)); + } + return nil; +} + +- (NSString*)suggestedFileExtension +{ + NSString *contentType = [self contentType]; + NSString *suggestedFileExtension = nil; + + if ([contentType isEqualToString:@"audio/mpeg"]) { + suggestedFileExtension = @"mp3"; + } else if ([contentType isEqualToString:@"audio/x-wav"]) { + suggestedFileExtension = @"wav"; + } else if ([contentType isEqualToString:@"audio/x-aifc"]) { + suggestedFileExtension = @"aifc"; + } else if ([contentType isEqualToString:@"audio/x-aiff"]) { + suggestedFileExtension = @"aiff"; + } else if ([contentType isEqualToString:@"audio/x-m4a"]) { + suggestedFileExtension = @"m4a"; + } else if ([contentType isEqualToString:@"audio/mp4"]) { + suggestedFileExtension = @"mp4"; + } else if ([contentType isEqualToString:@"audio/x-caf"]) { + suggestedFileExtension = @"caf"; + } + else if ([contentType isEqualToString:@"audio/aac"] || + [contentType isEqualToString:@"audio/aacp"]) { + suggestedFileExtension = @"aac"; + } + return suggestedFileExtension; +} + +- (UInt64)defaultContentLength +{ + return _audioStream->defaultContentLength(); +} + +- (UInt64)contentLength +{ + return _audioStream->contentLength(); +} + +- (NSURL*)outputFile +{ + CFURLRef url = _audioStream->outputFile(); + if (url) { + NSURL *u = (__bridge NSURL*)url; + return [u copy]; + } + return nil; +} + +- (void)setOutputFile:(NSURL *)outputFile +{ + if (!outputFile) { + _audioStream->setOutputFile(NULL); + return; + } + NSURL *copyOfURL = [outputFile copy]; + _audioStream->setOutputFile((__bridge CFURLRef)copyOfURL); +} + +- (FSStreamStatistics *)statistics +{ + FSStreamStatistics *stats = [[FSStreamStatistics alloc] init]; + + stats.snapshotTime = [[NSDate alloc] init]; + stats.audioStreamPacketCount = _audioStream->playbackDataCount(); + + return stats; +} + +- (FSLevelMeterState)levels +{ + AudioQueueLevelMeterState aqLevels = _audioStream->levels(); + + FSLevelMeterState l; + + l.averagePower = aqLevels.mAveragePower; + l.peakPower = aqLevels.mPeakPower; + + return l; +} + +- (size_t)prebufferedByteCount +{ + return _audioStream->cachedDataSize(); +} + +- (FSSeekByteOffset)currentSeekByteOffset +{ + FSSeekByteOffset offset; + offset.start = 0; + offset.end = 0; + offset.position = 0; + + // If continuous + if (!([self durationInSeconds] > 0)) { + return offset; + } + + offset.position = _audioStream->playbackPosition().offset; + + astreamer::Input_Stream_Position httpStreamPos = _audioStream->streamPositionForOffset(offset.position); + + offset.start = httpStreamPos.start; + offset.end = httpStreamPos.end; + + return offset; +} + +- (float)bitRate +{ + return _audioStream->bitrate(); +} + +- (FSStreamConfiguration *)configuration +{ + FSStreamConfiguration *config = [[FSStreamConfiguration alloc] init]; + + astreamer::Stream_Configuration *c = astreamer::Stream_Configuration::configuration(); + + config.bufferCount = c->bufferCount; + config.bufferSize = c->bufferSize; + config.maxPacketDescs = c->maxPacketDescs; + config.httpConnectionBufferSize = c->httpConnectionBufferSize; + config.outputSampleRate = c->outputSampleRate; + config.outputNumChannels = c->outputNumChannels; + config.bounceInterval = c->bounceInterval; + config.maxBounceCount = c->maxBounceCount; + config.startupWatchdogPeriod = c->startupWatchdogPeriod; + config.maxPrebufferedByteCount = c->maxPrebufferedByteCount; + config.usePrebufferSizeCalculationInSeconds = c->usePrebufferSizeCalculationInSeconds; + config.usePrebufferSizeCalculationInPackets = c->usePrebufferSizeCalculationInPackets; + config.requiredInitialPrebufferedByteCountForContinuousStream = c->requiredInitialPrebufferedByteCountForContinuousStream; + config.requiredInitialPrebufferedByteCountForNonContinuousStream = c->requiredInitialPrebufferedByteCountForNonContinuousStream; + config.requiredPrebufferSizeInSeconds = c->requiredPrebufferSizeInSeconds; + config.requiredInitialPrebufferedPacketCount = c->requiredInitialPrebufferedPacketCount; + config.cacheEnabled = c->cacheEnabled; + config.seekingFromCacheEnabled = c->seekingFromCacheEnabled; + config.automaticAudioSessionHandlingEnabled = c->automaticAudioSessionHandlingEnabled; + config.enableTimeAndPitchConversion = c->enableTimeAndPitchConversion; + config.requireStrictContentTypeChecking = c->requireStrictContentTypeChecking; + config.maxDiskCacheSize = c->maxDiskCacheSize; + + if (c->userAgent) { + // Let the Objective-C side handle the memory for the copy of the original user-agent + config.userAgent = (__bridge_transfer NSString *)CFStringCreateCopy(kCFAllocatorDefault, c->userAgent); + } + + if (c->cacheDirectory) { + config.cacheDirectory = (__bridge_transfer NSString *)CFStringCreateCopy(kCFAllocatorDefault, c->cacheDirectory); + } + + if (c->predefinedHttpHeaderValues) { + config.predefinedHttpHeaderValues = (__bridge_transfer NSDictionary *)CFDictionaryCreateCopy(kCFAllocatorDefault, c->predefinedHttpHeaderValues); + } + + return config; +} + +- (NSString *)formatDescription +{ + return CFBridgingRelease(_audioStream->sourceFormatDescription()); +} + +- (BOOL)cached +{ + BOOL cachedFileExists = NO; + + if (self.url) { + NSString *cacheIdentifier = (NSString*)CFBridgingRelease(_audioStream->createCacheIdentifierForURL((__bridge CFURLRef)self.url)); + + NSString *fullPath = [NSString stringWithFormat:@"%@/%@.metadata", self.configuration.cacheDirectory, cacheIdentifier]; + + cachedFileExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath]; + } + + return cachedFileExists; +} + +- (void)reachabilityChanged:(NSNotification *)note +{ + NSAssert([NSThread isMainThread], @"FSAudioStreamPrivate.reachabilityChanged needs to be called in the main thread"); + + Reachability *reach = [note object]; + NetworkStatus netStatus = [reach currentReachabilityStatus]; + self.internetConnectionAvailable = (netStatus == ReachableViaWiFi || netStatus == ReachableViaWWAN); + + if ([self isPlaying] && !self.internetConnectionAvailable) { + self.wasDisconnected = YES; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Error: Internet connection disconnected while playing a stream."); +#endif + } + + if (self.wasDisconnected && self.internetConnectionAvailable) { + self.wasDisconnected = NO; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Internet connection available again."); +#endif + [self attemptRestart]; + } +} + +- (void)interruptionOccurred:(NSNotification *)notification +{ + NSAssert([NSThread isMainThread], @"FSAudioStreamPrivate.interruptionOccurred needs to be called in the main thread"); + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + NSNumber *interruptionType = [[notification userInfo] valueForKey:AVAudioSessionInterruptionTypeKey]; + NSNumber *interruptionResume = [[notification userInfo] valueForKey:AVAudioSessionInterruptionOptionKey]; + if ([interruptionType intValue] == AVAudioSessionInterruptionTypeBegan) { + if ([self isPlaying] && !_wasPaused) { + self.wasInterrupted = YES; + // Continuous streams do not have a duration. + self.wasContinuousStream = !([self durationInSeconds] > 0); + + if (self.wasContinuousStream) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Interruption began. Continuous stream. Stopping the stream."); +#endif + [self stop]; + } else { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Interruption began. Non-continuous stream. Stopping the stream and saving the offset."); +#endif + _lastSeekByteOffset = [self currentSeekByteOffset]; + [self stop]; + } + } + } else if ([interruptionType intValue] == AVAudioSessionInterruptionTypeEnded) { + if (self.wasInterrupted) { + self.wasInterrupted = NO; + + if ([interruptionResume intValue] == AVAudioSessionInterruptionOptionShouldResume) { + @synchronized (self) { + if (self.configuration.automaticAudioSessionHandlingEnabled) { + [[AVAudioSession sharedInstance] setActive:YES error:nil]; + } + fsAudioStreamPrivateActiveSessions[[NSNumber numberWithUnsignedLong:(unsigned long)self]] = @""; + } + + if (self.wasContinuousStream) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Interruption ended. Continuous stream. Starting the playback."); +#endif + /* + * Resume playing. + */ + [self play]; + } else { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Interruption ended. Continuous stream. Playing from the offset"); +#endif + /* + * Resume playing. + */ + [self playFromOffset:_lastSeekByteOffset]; + } + } else { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Interruption ended. Continuous stream. Not resuming."); +#endif + } + } + } +#endif +} + +- (void)notifyPlaybackStopped +{ +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + @synchronized (self) { + [fsAudioStreamPrivateActiveSessions removeObjectForKey:[NSNumber numberWithUnsignedLong:(unsigned long)self]]; + + if ([fsAudioStreamPrivateActiveSessions count] == 0) { + if (self.configuration.automaticAudioSessionHandlingEnabled) { +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; +#else + [[AVAudioSession sharedInstance] setActive:NO error:nil]; +#endif + } + } + } +#endif + + [self notifyStateChange:kFsAudioStreamStopped]; +} + +- (void)notifyPlaybackBuffering +{ + self.internetConnectionAvailable = YES; + [self notifyStateChange:kFsAudioStreamBuffering]; +} + +- (void)notifyPlaybackPlaying +{ +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + @synchronized (self) { + if (self.configuration.automaticAudioSessionHandlingEnabled) { + [[AVAudioSession sharedInstance] setActive:YES error:nil]; + } + fsAudioStreamPrivateActiveSessions[[NSNumber numberWithUnsignedLong:(unsigned long)self]] = @""; + } +#endif + if (self.retryCount > 0) { + [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(notifyRetryingSucceeded) + userInfo:nil + repeats:NO]; + } + self.retryCount = 0; + [self notifyStateChange:kFsAudioStreamPlaying]; +} + +- (void)notifyPlaybackPaused +{ + [self notifyStateChange:kFsAudioStreamPaused]; +} + +- (void)notifyPlaybackSeeking +{ + [self notifyStateChange:kFsAudioStreamSeeking]; +} + +- (void)notifyPlaybackEndOfFile +{ + [self notifyStateChange:kFSAudioStreamEndOfFile]; +} + +- (void)notifyPlaybackFailed +{ +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + @synchronized (self) { + [fsAudioStreamPrivateActiveSessions removeObjectForKey:[NSNumber numberWithUnsignedLong:(unsigned long)self]]; + + if ([fsAudioStreamPrivateActiveSessions count] == 0) { + if (self.configuration.automaticAudioSessionHandlingEnabled) { +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000) + [[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; +#else + [[AVAudioSession sharedInstance] setActive:NO error:nil]; +#endif + } + } + } +#endif + + [self notifyStateChange:kFsAudioStreamFailed]; +} + +- (void)notifyPlaybackCompletion +{ + [self notifyStateChange:kFsAudioStreamPlaybackCompleted]; + + if (self.onCompletion) { + self.onCompletion(); + } +} + +- (void)notifyPlaybackUnknownState +{ + [self notifyStateChange:kFsAudioStreamUnknownState]; +} + +- (void)notifyRetryingStarted +{ + [self notifyStateChange:kFsAudioStreamRetryingStarted]; +} + +- (void)notifyRetryingSucceeded +{ + [self notifyStateChange:kFsAudioStreamRetryingSucceeded]; +} + +- (void)notifyRetryingFailed +{ + [self notifyStateChange:kFsAudioStreamRetryingFailed]; +} + +- (void)notifyStateChange:(FSAudioStreamState)streamerState +{ + if (self.onStateChange) { + self.onStateChange(streamerState); + } + + NSDictionary *userInfo = @{FSAudioStreamNotificationKey_State: [NSNumber numberWithInt:streamerState], + FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:_audioStream]}; + NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamStateChangeNotification object:self.stream userInfo:userInfo]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; +} + +- (void)preload +{ + _audioStream->setPreloading(true); + + _audioStream->open(); +} + +- (void)attemptRestart +{ + if (_audioStream->isPreloading()) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Stream is preloading. Not attempting a restart"); +#endif + return; + } + + if (_wasPaused) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Stream was paused. Not attempting a restart"); +#endif + return; + } + + if (!self.internetConnectionAvailable) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Internet connection not available. Not attempting a restart"); +#endif + return; + } + + if (self.retryCount >= self.maxRetryCount) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Retry count %lu. Giving up.", (unsigned long)self.retryCount); +#endif + [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(notifyRetryingFailed) + userInfo:nil + repeats:NO]; + return; + } + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Attempting restart."); +#endif + + [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(notifyRetryingStarted) + userInfo:nil + repeats:NO]; + + [NSTimer scheduledTimerWithTimeInterval:1 + target:self + selector:@selector(play) + userInfo:nil + repeats:NO]; + + self.retryCount++; +} + +- (void)expungeCache +{ + for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.configuration.cacheDirectory error:nil]) { + NSString *fullPath = [NSString stringWithFormat:@"%@/%@", self.configuration.cacheDirectory, file]; + + if ([file hasPrefix:@"FSCache-"]) { + if (![[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil]) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"Failed expunging %@ from the cache", fullPath); +#endif + } + } + } +} + +- (void)play +{ + _wasPaused = NO; + + if (_audioStream->isPreloading()) { + _audioStream->startCachedDataPlayback(); + + return; + } + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000) + [self endBackgroundTask]; + + _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + [self endBackgroundTask]; + }]; +#endif + + _audioStream->open(); + + if (!_reachability) { + _reachability = [Reachability reachabilityForInternetConnection]; + + [_reachability startNotifier]; + } +} + +- (void)stop +{ + _audioStream->close(true); + + [self endBackgroundTask]; + + [_reachability stopNotifier]; + _reachability = nil; +} + +- (BOOL)isPlaying +{ + const astreamer::Audio_Stream::State currentState = _audioStream->state(); + + return (currentState == astreamer::Audio_Stream::PLAYING || + currentState == astreamer::Audio_Stream::END_OF_FILE); +} + +- (void)pause +{ + _wasPaused = YES; + _audioStream->pause(); +} + +- (void)rewind:(unsigned int)seconds +{ + if (([self durationInSeconds] > 0)) { + // Rewinding only possible for continuous streams + return; + } + + const float originalVolume = [self currentVolume]; + + // Set volume to 0 to avoid glitches + _audioStream->setVolume(0); + + _audioStream->rewind(seconds); + + __weak FSAudioStreamPrivate *weakSelf = self; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + FSAudioStreamPrivate *strongSelf = weakSelf; + + // Return the original volume back + strongSelf->_audioStream->setVolume(originalVolume); + }); +} + +- (void)seekToOffset:(float)offset +{ + _audioStream->seekToOffset(offset); +} + +- (float)currentVolume +{ + return _audioStream->currentVolume(); +} + +- (unsigned long long)totalCachedObjectsSize +{ + unsigned long long totalCacheSize = 0; + for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.configuration.cacheDirectory error:nil]) { + if ([file hasPrefix:@"FSCache-"]) { + NSString *fullPath = [NSString stringWithFormat:@"%@/%@", self.configuration.cacheDirectory, file]; + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil]; + + totalCacheSize += [[attributes objectForKey:NSFileSize] longLongValue]; + } + } + return totalCacheSize; +} + +- (void)setVolume:(float)volume +{ + _audioStream->setVolume(volume); +} + +- (void)setPlayRate:(float)playRate +{ + _audioStream->setPlayRate(playRate); +} + +- (astreamer::AS_Playback_Position)playbackPosition +{ + return _audioStream->playbackPosition(); +} + +- (UInt64)audioDataByteCount +{ + return _audioStream->audioDataByteCount(); +} + +- (float)durationInSeconds +{ + return _audioStream->durationInSeconds(); +} + +- (void)bitrateAvailable +{ + if (!self.configuration.usePrebufferSizeCalculationInSeconds) { + return; + } + + float bitrate = (int)_audioStream->bitrate(); + + if (!(bitrate > 0)) { + // No bitrate provided, use the defaults + return; + } + + const Float64 bufferSizeForSecond = bitrate / 8.0; + + int bufferSize = (bufferSizeForSecond * self.configuration.requiredPrebufferSizeInSeconds); + + // Check that we still got somewhat sane buffer size + if (bufferSize < 50000) { + bufferSize = 50000; + } + + if (!([self durationInSeconds] > 0)) { + // continuous + if (bufferSize > self.configuration.requiredInitialPrebufferedByteCountForContinuousStream) { + bufferSize = self.configuration.requiredInitialPrebufferedByteCountForContinuousStream; + } + } else { + if (bufferSize > self.configuration.requiredInitialPrebufferedByteCountForNonContinuousStream) { + bufferSize = self.configuration.requiredInitialPrebufferedByteCountForNonContinuousStream; + } + } + + // Update the configuration + self.configuration.requiredInitialPrebufferedByteCountForContinuousStream = bufferSize; + self.configuration.requiredInitialPrebufferedByteCountForNonContinuousStream = bufferSize; + + astreamer::Stream_Configuration *c = astreamer::Stream_Configuration::configuration(); + + c->requiredInitialPrebufferedByteCountForContinuousStream = bufferSize; + c->requiredInitialPrebufferedByteCountForNonContinuousStream = bufferSize; +} + +-(NSString *)description +{ + return [NSString stringWithFormat:@"[FreeStreamer %@] URL: %@\nbufferCount: %i\nbufferSize: %i\nmaxPacketDescs: %i\nhttpConnectionBufferSize: %i\noutputSampleRate: %f\noutputNumChannels: %ld\nbounceInterval: %i\nmaxBounceCount: %i\nstartupWatchdogPeriod: %i\nmaxPrebufferedByteCount: %i\nformat: %@\nbit rate: %f\nuserAgent: %@\ncacheDirectory: %@\npredefinedHttpHeaderValues: %@\ncacheEnabled: %@\nseekingFromCacheEnabled: %@\nautomaticAudioSessionHandlingEnabled: %@\nenableTimeAndPitchConversion: %@\nrequireStrictContentTypeChecking: %@\nmaxDiskCacheSize: %i\nusePrebufferSizeCalculationInSeconds: %@\nusePrebufferSizeCalculationInPackets: %@\nrequiredPrebufferSizeInSeconds: %f\nrequiredInitialPrebufferedByteCountForContinuousStream: %i\nrequiredInitialPrebufferedByteCountForNonContinuousStream: %i\nrequiredInitialPrebufferedPacketCount: %i", + freeStreamerReleaseVersion(), + self.url, + self.configuration.bufferCount, + self.configuration.bufferSize, + self.configuration.maxPacketDescs, + self.configuration.httpConnectionBufferSize, + self.configuration.outputSampleRate, + self.configuration.outputNumChannels, + self.configuration.bounceInterval, + self.configuration.maxBounceCount, + self.configuration.startupWatchdogPeriod, + self.configuration.maxPrebufferedByteCount, + self.formatDescription, + self.bitRate, + self.configuration.userAgent, + self.configuration.cacheDirectory, + self.configuration.predefinedHttpHeaderValues, + (self.configuration.cacheEnabled ? @"YES" : @"NO"), + (self.configuration.seekingFromCacheEnabled ? @"YES" : @"NO"), + (self.configuration.automaticAudioSessionHandlingEnabled ? @"YES" : @"NO"), + (self.configuration.enableTimeAndPitchConversion ? @"YES" : @"NO"), + (self.configuration.requireStrictContentTypeChecking ? @"YES" : @"NO"), + self.configuration.maxDiskCacheSize, + (self.configuration.usePrebufferSizeCalculationInSeconds ? @"YES" : @"NO"), + (self.configuration.usePrebufferSizeCalculationInPackets ? @"YES" : @"NO"), + self.configuration.requiredPrebufferSizeInSeconds, + self.configuration.requiredInitialPrebufferedByteCountForContinuousStream, + self.configuration.requiredInitialPrebufferedByteCountForNonContinuousStream, + self.configuration.requiredInitialPrebufferedPacketCount]; +} + +@end + +/* + * =============================================================== + * FSAudioStream public implementation, merely wraps the + * private class. + * =============================================================== + */ + +@implementation FSAudioStream + +-(id)init +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.init needs to be called in the main thread"); + + FSStreamConfiguration *defaultConfiguration = [[FSStreamConfiguration alloc] init]; + + if (self = [self initWithConfiguration:defaultConfiguration]) { + } + return self; +} + +- (id)initWithUrl:(NSURL *)url +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.initWithURL needs to be called in the main thread"); + + if (self = [self init]) { + _private.url = url; + } + return self; +} + +- (id)initWithConfiguration:(FSStreamConfiguration *)configuration +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.initWithConfiguration needs to be called in the main thread"); + + if (self = [super init]) { + astreamer::Stream_Configuration *c = astreamer::Stream_Configuration::configuration(); + + c->bufferCount = configuration.bufferCount; + c->bufferSize = configuration.bufferSize; + c->maxPacketDescs = configuration.maxPacketDescs; + c->httpConnectionBufferSize = configuration.httpConnectionBufferSize; + c->outputSampleRate = configuration.outputSampleRate; + c->outputNumChannels = configuration.outputNumChannels; + c->maxBounceCount = configuration.maxBounceCount; + c->bounceInterval = configuration.bounceInterval; + c->startupWatchdogPeriod = configuration.startupWatchdogPeriod; + c->maxPrebufferedByteCount = configuration.maxPrebufferedByteCount; + c->usePrebufferSizeCalculationInSeconds = configuration.usePrebufferSizeCalculationInSeconds; + c->usePrebufferSizeCalculationInPackets = configuration.usePrebufferSizeCalculationInPackets; + c->cacheEnabled = configuration.cacheEnabled; + c->seekingFromCacheEnabled = configuration.seekingFromCacheEnabled; + c->automaticAudioSessionHandlingEnabled = configuration.automaticAudioSessionHandlingEnabled; + c->enableTimeAndPitchConversion = configuration.enableTimeAndPitchConversion; + c->requireStrictContentTypeChecking = configuration.requireStrictContentTypeChecking; + c->maxDiskCacheSize = configuration.maxDiskCacheSize; + c->requiredInitialPrebufferedByteCountForContinuousStream = configuration.requiredInitialPrebufferedByteCountForContinuousStream; + c->requiredInitialPrebufferedByteCountForNonContinuousStream = configuration.requiredInitialPrebufferedByteCountForNonContinuousStream; + c->requiredPrebufferSizeInSeconds = configuration.requiredPrebufferSizeInSeconds; + c->requiredInitialPrebufferedPacketCount = configuration.requiredInitialPrebufferedPacketCount; + + if (c->userAgent) { + CFRelease(c->userAgent); + } + c->userAgent = CFStringCreateCopy(kCFAllocatorDefault, (__bridge CFStringRef)configuration.userAgent); + + if (c->cacheDirectory) { + CFRelease(c->cacheDirectory); + } + if (configuration.cacheDirectory) { + c->cacheDirectory = CFStringCreateCopy(kCFAllocatorDefault, (__bridge CFStringRef)configuration.cacheDirectory); + } else { + c->cacheDirectory = NULL; + } + + if (c->predefinedHttpHeaderValues) { + CFRelease(c->predefinedHttpHeaderValues); + } + if (configuration.predefinedHttpHeaderValues) { + c->predefinedHttpHeaderValues = CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)configuration.predefinedHttpHeaderValues); + } else { + c->predefinedHttpHeaderValues = NULL; + } + + _private = [[FSAudioStreamPrivate alloc] init]; + _private.stream = self; + } + return self; +} + +- (void)dealloc +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.dealloc needs to be called in the main thread"); + + AudioStreamStateObserver *observer = [_private streamStateObserver]; + + // Break the cyclic loop so that dealloc() may be called + observer->priv = nil; + + _private.stream = nil; + _private.delegate = nil; + + _private = nil; +} + +- (void)setUrl:(NSURL *)url +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setUrl needs to be called in the main thread"); + + [_private setUrl:url]; +} + +- (NSURL*)url +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.url needs to be called in the main thread"); + + return [_private url]; +} + +- (void)setStrictContentTypeChecking:(BOOL)strictContentTypeChecking +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setStrictContentTypeChecking needs to be called in the main thread"); + + [_private setStrictContentTypeChecking:strictContentTypeChecking]; +} + +- (BOOL)strictContentTypeChecking +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.strictContentTypeChecking needs to be called in the main thread"); + + return [_private strictContentTypeChecking]; +} + +- (NSURL*)outputFile +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.outputFile needs to be called in the main thread"); + + return [_private outputFile]; +} + +- (void)setOutputFile:(NSURL *)outputFile +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setOutputFile needs to be called in the main thread"); + + [_private setOutputFile:outputFile]; +} + +- (void)setDefaultContentType:(NSString *)defaultContentType +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setDefaultContentType needs to be called in the main thread"); + + [_private setDefaultContentType:defaultContentType]; +} + +- (NSString*)defaultContentType +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.defaultContentType needs to be called in the main thread"); + + return [_private defaultContentType]; +} + +- (NSString*)contentType +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.contentType needs to be called in the main thread"); + + return [_private contentType]; +} + +- (NSString*)suggestedFileExtension +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.suggestedFileExtension needs to be called in the main thread"); + + return [_private suggestedFileExtension]; +} + +- (UInt64)defaultContentLength +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.defaultContentLength needs to be called in the main thread"); + + return [_private defaultContentLength]; +} + +- (UInt64)contentLength +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.contentLength needs to be called in the main thread"); + + return [_private contentLength]; +} + +- (UInt64)audioDataByteCount +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.audioDataByteCount needs to be called in the main thread"); + + return [_private audioDataByteCount]; +} + +- (void)preload +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.preload needs to be called in the main thread"); + + [_private preload]; +} + +- (void)play +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.play needs to be called in the main thread"); + + [_private play]; +} + +- (void)playFromURL:(NSURL*)url +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.playFromURL needs to be called in the main thread"); + + [_private playFromURL:url]; +} + +- (void)playFromOffset:(FSSeekByteOffset)offset +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.playFromOffset needs to be called in the main thread"); + + [_private playFromOffset:offset]; +} + +- (void)stop +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.stop needs to be called in the main thread"); + + [_private stop]; +} + +- (void)pause +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.pause needs to be called in the main thread"); + + [_private pause]; +} + +- (void)rewind:(unsigned int)seconds +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.rewind needs to be called in the main thread"); + + [_private rewind:seconds]; +} + +- (void)seekToPosition:(FSStreamPosition)position +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.seekToPosition needs to be called in the main thread"); + + if (!(position.position > 0)) { + // To retain compatibility with older implementations, + // fallback to using less accurate position.minute and position.second, if needed + const float seekTime = position.minute * 60 + position.second; + + position.position = seekTime / [_private durationInSeconds]; + } + + [_private seekToOffset:position.position]; +} + +- (void)setPlayRate:(float)playRate +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setPlayRate needs to be called in the main thread"); + + [_private setPlayRate:playRate]; +} + +- (BOOL)isPlaying +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.isPlaying needs to be called in the main thread"); + + return [_private isPlaying]; +} + +- (void)expungeCache +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.expungeCache needs to be called in the main thread"); + + [_private expungeCache]; +} + +- (NSUInteger)retryCount +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.retryCount needs to be called in the main thread"); + + return _private.retryCount; +} + +- (FSStreamStatistics *)statistics +{ + return _private.statistics; +} + +- (FSLevelMeterState)levels +{ + return _private.levels; +} + +- (FSStreamPosition)currentTimePlayed +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.currentTimePlayed needs to be called in the main thread"); + + FSStreamPosition pos; + pos.position = 0; + pos.playbackTimeInSeconds = [_private playbackPosition].timePlayed; + pos.minute = 0; + pos.second = 0; + + const float durationInSeconds = [_private durationInSeconds]; + + if (durationInSeconds > 0) { + pos.position = pos.playbackTimeInSeconds / [_private durationInSeconds]; + } + + // Extract the minutes and seconds for convenience + if (pos.playbackTimeInSeconds > 0) { + unsigned u = pos.playbackTimeInSeconds; + unsigned s,m; + + s = u % 60; + u /= 60; + m = u; + + pos.minute = m; + pos.second = s; + } + + return pos; +} + +- (FSStreamPosition)duration +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.duration needs to be called in the main thread"); + + FSStreamPosition pos; + pos.minute = 0; + pos.second = 0; + pos.playbackTimeInSeconds = 0; + pos.position = 0; + + const float durationInSeconds = [_private durationInSeconds]; + + if (durationInSeconds > 0) { + unsigned u = durationInSeconds; + + unsigned s,m; + + s = u % 60; + u /= 60; + m = u; + + pos.minute = m; + pos.second = s; + } + + pos.playbackTimeInSeconds = durationInSeconds; + + return pos; +} + +- (FSSeekByteOffset)currentSeekByteOffset +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.currentSeekByteOffset needs to be called in the main thread"); + + return _private.currentSeekByteOffset; +} + +- (float)bitRate +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.bitRate needs to be called in the main thread"); + + return _private.bitRate; +} + +- (BOOL)continuous +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.continuous needs to be called in the main thread"); + + return !([_private durationInSeconds] > 0); +} + +- (BOOL)cached +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.cached needs to be called in the main thread"); + + return _private.cached; +} + +- (size_t)prebufferedByteCount +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.prebufferedByteCount needs to be called in the main thread"); + + return _private.prebufferedByteCount; +} + +- (float)volume +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.volume needs to be called in the main thread"); + + return [_private currentVolume]; +} + +- (unsigned long long)totalCachedObjectsSize +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.totalCachedObjectsSize needs to be called in the main thread"); + + return [_private totalCachedObjectsSize]; +} + +- (void)setVolume:(float)volume +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setVolume needs to be called in the main thread"); + + [_private setVolume:volume]; +} + +- (void (^)())onCompletion +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.onCompletion needs to be called in the main thread"); + + return _private.onCompletion; +} + +- (void)setOnCompletion:(void (^)())onCompletion +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setOnCompletion needs to be called in the main thread"); + + _private.onCompletion = onCompletion; +} + +- (void (^)(FSAudioStreamState state))onStateChange +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.onStateChange needs to be called in the main thread"); + + return _private.onStateChange; +} + +- (void (^)(NSDictionary *metaData))onMetaDataAvailable +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.onMetaDataAvailable needs to be called in the main thread"); + + return _private.onMetaDataAvailable; +} + +- (void (^)(FSAudioStreamError error, NSString *errorDescription))onFailure +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.onFailure needs to be called in the main thread"); + + return _private.onFailure; +} + +- (void)setOnStateChange:(void (^)(FSAudioStreamState))onStateChange +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setOnStateChange needs to be called in the main thread"); + + _private.onStateChange = onStateChange; +} + +- (void)setOnMetaDataAvailable:(void (^)(NSDictionary *))onMetaDataAvailable +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setOnMetaDataAvailable needs to be called in the main thread"); + + _private.onMetaDataAvailable = onMetaDataAvailable; +} + +- (void)setOnFailure:(void (^)(FSAudioStreamError error, NSString *errorDescription))onFailure +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setOnFailure needs to be called in the main thread"); + + _private.onFailure = onFailure; +} + +- (FSStreamConfiguration *)configuration +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.configuration needs to be called in the main thread"); + + return _private.configuration; +} + +- (void)setDelegate:(id)delegate +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setDelegate needs to be called in the main thread"); + + _private.delegate = delegate; +} + +- (id)delegate +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.delegate needs to be called in the main thread"); + + return _private.delegate; +} + +-(NSString *)description +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.description needs to be called in the main thread"); + + return [_private description]; +} + +-(NSUInteger)maxRetryCount +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.maxRetryCount needs to be called in the main thread"); + + return [_private maxRetryCount]; +} + +-(void)setMaxRetryCount:(NSUInteger)maxRetryCount +{ + NSAssert([NSThread isMainThread], @"FSAudioStream.setMaxRetryCount needs to be called in the main thread"); + + [_private setMaxRetryCount:maxRetryCount]; +} + +@end + +/* + * =============================================================== + * AudioStreamStateObserver: listen to the state from the audio stream. + * =============================================================== + */ + +void AudioStreamStateObserver::audioStreamErrorOccurred(int errorCode, CFStringRef errorDescription) +{ + FSAudioStreamError error = kFsAudioStreamErrorNone; + + NSString *errorForObjC = @""; + + if (errorDescription) { + errorForObjC = CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, errorDescription)); + } + + switch (errorCode) { + case kFsAudioStreamErrorOpen: + error = kFsAudioStreamErrorOpen; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Error opening the stream: %@ %@", errorForObjC, priv); +#endif + + break; + case kFsAudioStreamErrorStreamParse: + error = kFsAudioStreamErrorStreamParse; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Error parsing the stream: %@ %@", errorForObjC, priv); +#endif + + break; + case kFsAudioStreamErrorNetwork: + error = kFsAudioStreamErrorNetwork; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Network error: %@ %@", errorForObjC, priv); +#endif + + break; + case kFsAudioStreamErrorUnsupportedFormat: + error = kFsAudioStreamErrorUnsupportedFormat; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Unsupported format error: %@ %@", errorForObjC, priv); +#endif + + break; + + case kFsAudioStreamErrorStreamBouncing: + error = kFsAudioStreamErrorStreamBouncing; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Stream bounced: %@ %@", errorForObjC, priv); +#endif + + break; + + case kFsAudioStreamErrorTerminated: + error = kFsAudioStreamErrorTerminated; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSAudioStream: Stream terminated: %@ %@", errorForObjC, priv); +#endif + break; + + default: + break; + } + + if (priv.onFailure) { + priv.onFailure(error, errorForObjC); + } + + NSDictionary *userInfo = @{FSAudioStreamNotificationKey_Error: @(errorCode), + FSAudioStreamNotificationKey_ErrorDescription: errorForObjC, + FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]}; + NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamErrorNotification object:priv.stream userInfo:userInfo]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + + if (error == kFsAudioStreamErrorNetwork || + error == kFsAudioStreamErrorUnsupportedFormat || + error == kFsAudioStreamErrorOpen || + error == kFsAudioStreamErrorTerminated) { + + if (!source->isPreloading()) { + [priv attemptRestart]; + } + } +} + +void AudioStreamStateObserver::audioStreamStateChanged(astreamer::Audio_Stream::State state) +{ + SEL notificationHandler; + + switch (state) { + case astreamer::Audio_Stream::STOPPED: + notificationHandler = @selector(notifyPlaybackStopped); + break; + case astreamer::Audio_Stream::BUFFERING: + notificationHandler = @selector(notifyPlaybackBuffering); + break; + case astreamer::Audio_Stream::PLAYING: + [priv endBackgroundTask]; + + notificationHandler = @selector(notifyPlaybackPlaying); + break; + case astreamer::Audio_Stream::PAUSED: + notificationHandler = @selector(notifyPlaybackPaused); + break; + case astreamer::Audio_Stream::SEEKING: + notificationHandler = @selector(notifyPlaybackSeeking); + break; + case astreamer::Audio_Stream::END_OF_FILE: + notificationHandler = @selector(notifyPlaybackEndOfFile); + break; + case astreamer::Audio_Stream::FAILED: + [priv endBackgroundTask]; + + notificationHandler = @selector(notifyPlaybackFailed); + break; + case astreamer::Audio_Stream::PLAYBACK_COMPLETED: + notificationHandler = @selector(notifyPlaybackCompletion); + break; + default: + // Unknown state + notificationHandler = @selector(notifyPlaybackUnknownState); + break; + } + + // Detach from the player so that the event loop can complete its cycle. + // This ensures that the stream gets closed, if needs to be. + [NSTimer scheduledTimerWithTimeInterval:0 + target:priv + selector:notificationHandler + userInfo:nil + repeats:NO]; +} + +void AudioStreamStateObserver::audioStreamMetaDataAvailable(std::map metaData) +{ + NSMutableDictionary *metaDataDictionary = [[NSMutableDictionary alloc] init]; + + for (std::map::iterator iter = metaData.begin(); iter != metaData.end(); ++iter) { + CFStringRef key = iter->first; + CFStringRef value = iter->second; + + metaDataDictionary[CFBridgingRelease(key)] = CFBridgingRelease(value); + } + + if (priv.onMetaDataAvailable) { + priv.onMetaDataAvailable(metaDataDictionary); + } + + NSDictionary *userInfo = @{FSAudioStreamNotificationKey_MetaData: metaDataDictionary, + FSAudioStreamNotificationKey_Stream: [NSValue valueWithPointer:source]}; + NSNotification *notification = [NSNotification notificationWithName:FSAudioStreamMetaDataNotification object:priv.stream userInfo:userInfo]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; +} + +void AudioStreamStateObserver::samplesAvailable(AudioBufferList *samples, UInt32 frames, AudioStreamPacketDescription description) +{ + if ([priv.delegate respondsToSelector:@selector(audioStream:samplesAvailable:frames:description:)]) { + [priv.delegate audioStream:priv.stream samplesAvailable:samples frames:frames description:description]; + } +} + +void AudioStreamStateObserver::bitrateAvailable() +{ + [priv bitrateAvailable]; +} diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.h new file mode 100644 index 0000000..17cb59a --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.h @@ -0,0 +1,128 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import + +/** + * Content type format. + */ +typedef NS_ENUM(NSInteger, FSFileFormat) { + /** + * Unknown format. + */ + kFSFileFormatUnknown = 0, + + /** + * M3U playlist. + */ + kFSFileFormatM3UPlaylist, + /** + * PLS playlist. + */ + kFSFileFormatPLSPlaylist, + + /** + * XML file. + */ + kFSFileFormatXML, + + /** + * MP3 file. + */ + kFSFileFormatMP3, + /** + * WAVE file. + */ + kFSFileFormatWAVE, + /** + * AIFC file. + */ + kFSFileFormatAIFC, + /** + * AIFF file. + */ + kFSFileFormatAIFF, + /** + * M4A file. + */ + kFSFileFormatM4A, + /** + * MPEG4 file. + */ + kFSFileFormatMPEG4, + /** + * CAF file. + */ + kFSFileFormatCAF, + /** + * AAC_ADTS file. + */ + kFSFileFormatAAC_ADTS, + + /** + * Total number of formats. + */ + kFSFileFormatCount +}; + +/** + * FSCheckContentTypeRequest is a class for checking the content type + * of a URL. It makes an HTTP HEAD request and parses the header information + * from the server. The resulting format is stored in the format property. + * + * To use the class, define the URL for checking the content type using + * the url property. Then, define the onCompletion and onFailure handlers. + * To start the request, use the start method. + */ +@interface FSCheckContentTypeRequest : NSObject { + NSURLSessionTask *_task; + FSFileFormat _format; + NSString *_contentType; + BOOL _playlist; + BOOL _xml; +} + +/** + * The URL of this request. + */ +@property (nonatomic,copy) NSURL *url; +/** + * Called when the content type determination is completed. + */ +@property (copy) void (^onCompletion)(void); +/** + * Called if the content type determination failed. + */ +@property (copy) void (^onFailure)(void); +/** + * Contains the format of the URL upon completion of the request. + */ +@property (nonatomic,readonly) FSFileFormat format; +/** + * Containts the content type of the URL upon completion of the request. + */ +@property (nonatomic,readonly) NSString *contentType; +/** + * The property is true if the URL contains a playlist. + */ +@property (nonatomic,readonly) BOOL playlist; +/** + * The property is true if the URL contains XML data. + */ +@property (nonatomic,readonly) BOOL xml; + +/** + * Starts the request. + */ +- (void)start; +/** + * Cancels the request. + */ +- (void)cancel; + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.m b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.m new file mode 100644 index 0000000..180977b --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.m @@ -0,0 +1,236 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import "FSCheckContentTypeRequest.h" + +@interface FSCheckContentTypeRequest () +- (BOOL)guessContentTypeByUrl:(NSURLResponse *)response; +@end + +@implementation FSCheckContentTypeRequest + +- (id)init +{ + self = [super init]; + if (self) { + _format = kFSFileFormatUnknown; + _playlist = NO; + _xml = NO; + } + return self; +} + +- (void)start +{ + if (_task) { + return; + } + + _format = kFSFileFormatUnknown; + _playlist = NO; + _contentType = @""; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url + cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:10.0]; + [request setHTTPMethod:@"HEAD"]; + + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + + @synchronized (self) { + _task = [session dataTaskWithRequest:request]; + } + [_task resume]; + + if (!_task) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSCheckContentTypeRequest: Unable to open connection for URL: %@", _url); +#endif + + self.onFailure(); + return; + } +} + +- (void)cancel +{ + if (!_task) { + return; + } + @synchronized (self) { + [_task cancel]; + _task = nil; + } +} + +/* + * ======================================= + * Properties + * ======================================= + */ + +- (FSFileFormat)format +{ + return _format; +} + +- (NSString *)contentType +{ + return _contentType; +} + +- (BOOL)playlist +{ + return _playlist; +} + +- (BOOL)xml +{ + return _xml; +} + +/* + * ======================================= + * NSURLSessionDelegate + * ======================================= + */ + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + + _contentType = response.MIMEType; + + _format = kFSFileFormatUnknown; + _playlist = NO; + + NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; + + if (statusCode >= 200 && statusCode <= 299) { + // Only use the content type if the response indicated success (2xx) + + if ([_contentType isEqualToString:@"audio/mpeg"]) { + _format = kFSFileFormatMP3; + } else if ([_contentType isEqualToString:@"audio/x-wav"]) { + _format = kFSFileFormatWAVE; + } else if ([_contentType isEqualToString:@"audio/x-aifc"]) { + _format = kFSFileFormatAIFC; + } else if ([_contentType isEqualToString:@"audio/x-aiff"]) { + _format = kFSFileFormatAIFF; + } else if ([_contentType isEqualToString:@"audio/x-m4a"]) { + _format = kFSFileFormatM4A; + } else if ([_contentType isEqualToString:@"audio/mp4"]) { + _format = kFSFileFormatMPEG4; + } else if ([_contentType isEqualToString:@"audio/x-caf"]) { + _format = kFSFileFormatCAF; + } else if ([_contentType isEqualToString:@"audio/aac"] || + [_contentType isEqualToString:@"audio/aacp"]) { + _format = kFSFileFormatAAC_ADTS; + } else if ([_contentType isEqualToString:@"audio/x-mpegurl"] || + [_contentType isEqualToString:@"application/x-mpegurl"]) { + _format = kFSFileFormatM3UPlaylist; + _playlist = YES; + } else if ([_contentType isEqualToString:@"audio/x-scpls"] || + [_contentType isEqualToString:@"application/pls+xml"]) { + _format = kFSFileFormatPLSPlaylist; + _playlist = YES; + } else if ([_contentType isEqualToString:@"text/xml"] || + [_contentType isEqualToString:@"application/xml"]) { + _format = kFSFileFormatXML; + _xml = YES; + } else { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSCheckContentTypeRequest: Cannot resolve %@, guessing the content type by URL: %@", _contentType, _url); +#endif + [self guessContentTypeByUrl:response]; + } + } else { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSCheckContentTypeRequest: Invalid HTTP status code received %li, guessing the content type by URL: %@", (long)statusCode, _url); +#endif + [self guessContentTypeByUrl:response]; + } + + _task = nil; + + self.onCompletion(); +} + + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task +didCompleteWithError:(nullable NSError *)error { + + @synchronized (self) { + _task = nil; + _format = kFSFileFormatUnknown; + _playlist = NO; + } + + // Still, try if we could resolve the content type by the URL + if ([self guessContentTypeByUrl:nil]) { + self.onCompletion(); + } else { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSCheckContentTypeRequest: Unable to determine content-type for the URL: %@, error %@", _url, [error localizedDescription]); +#endif + + self.onFailure(); + } +} + +/* + * ======================================= + * Private + * ======================================= + */ + +- (BOOL)guessContentTypeByUrl:(NSURLResponse *)response +{ + /* The server did not provide meaningful content type; + last resort: check the file suffix, if there is one */ + + NSString *absoluteUrl; + + if (response) { + absoluteUrl = [response.URL absoluteString]; + } else { + absoluteUrl = [_url absoluteString]; + } + + if ([absoluteUrl hasSuffix:@".mp3"]) { + _format = kFSFileFormatMP3; + } else if ([absoluteUrl hasSuffix:@".mp4"]) { + _format = kFSFileFormatMPEG4; + } else if ([absoluteUrl hasSuffix:@".m3u"]) { + _format = kFSFileFormatM3UPlaylist; + _playlist = YES; + } else if ([absoluteUrl hasSuffix:@".pls"]) { + _format = kFSFileFormatPLSPlaylist; + _playlist = YES; + } else if ([absoluteUrl hasSuffix:@".xml"]) { + _format = kFSFileFormatXML; + _xml = YES; + } else { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSCheckContentTypeRequest: Failed to determine content type from the URL: %@", _url); +#endif + /* + * Failed to guess the content type based on the URL. + */ + return NO; + } + + /* + * We have determined a content-type. + */ + return YES; +} + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.h new file mode 100644 index 0000000..c85955e --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.h @@ -0,0 +1,71 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import + +/** + * The playlist format. + */ +typedef NS_ENUM(NSInteger, FSPlaylistFormat) { + /** + * Unknown playlist format. + */ + kFSPlaylistFormatNone, + /** + * M3U playlist. + */ + kFSPlaylistFormatM3U, + /** + * PLS playlist. + */ + kFSPlaylistFormatPLS +}; + +/** + * FSParsePlaylistRequest is a class for parsing a playlist. It supports + * the M3U and PLS formats. + * + * To use the class, define the URL for retrieving the playlist using + * the url property. Then, define the onCompletion and onFailure handlers. + * To start the request, use the start method. + */ +@interface FSParsePlaylistRequest : NSObject { + NSURLSessionTask *_task; + NSInteger _httpStatus; + NSMutableData *_receivedData; + NSMutableArray *_playlistItems; + FSPlaylistFormat _format; +} + +/** + * The URL of this request. + */ +@property (nonatomic,copy) NSURL *url; +/** + * Called when the playlist parsing is completed. + */ +@property (copy) void (^onCompletion)(void); +/** + * Called if the playlist parsing failed. + */ +@property (copy) void (^onFailure)(void); +/** + * The playlist items stored in the FSPlaylistItem class. + */ +@property (readonly) NSMutableArray *playlistItems; + +/** + * Starts the request. + */ +- (void)start; +/** + * Cancels the request. + */ +- (void)cancel; + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.m b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.m new file mode 100644 index 0000000..4415011 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParsePlaylistRequest.m @@ -0,0 +1,325 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import "FSParsePlaylistRequest.h" +#import "FSPlaylistItem.h" + +@interface FSParsePlaylistRequest () +- (void)parsePlaylistFromData:(NSData *)data; +- (void)parsePlaylistM3U:(NSString *)playlist; +- (void)parsePlaylistPLS:(NSString *)playlist; +- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl; + +@property (readonly) FSPlaylistFormat format; + +@end + +@implementation FSParsePlaylistRequest + +- (id)init +{ + self = [super init]; + if (self) { + } + return self; +} + +- (void)start +{ + if (_task) { + return; + } + + NSURLRequest *request = [NSURLRequest requestWithURL:self.url + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:10.0]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + + @synchronized (self) { + _receivedData = [NSMutableData data]; + _task = [session dataTaskWithRequest:request]; + _playlistItems = [[NSMutableArray alloc] init]; + _format = kFSPlaylistFormatNone; + } + + [_task resume]; +} + +- (void)cancel +{ + if (!_task) { + return; + } + @synchronized (self) { + [_task cancel]; + _task = nil; + } +} + +/* + * ======================================= + * Properties + * ======================================= + */ + +- (NSMutableArray *)playlistItems +{ + return [_playlistItems copy]; +} + +- (FSPlaylistFormat)format +{ + return _format; +} + +/* + * ======================================= + * Private + * ======================================= + */ + +- (void)parsePlaylistFromData:(NSData *)data +{ + NSString *playlistData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + + if (_format == kFSPlaylistFormatM3U) { + [self parsePlaylistM3U:playlistData]; + + if ([_playlistItems count] == 0) { + // If we failed to grab any playlist items, still try + // to parse it in another format; perhaps the server + // mistakingly identified the playlist format + + [self parsePlaylistPLS:playlistData]; + } + } else if (_format == kFSPlaylistFormatPLS) { + [self parsePlaylistPLS:playlistData]; + + if ([_playlistItems count] == 0) { + // If we failed to grab any playlist items, still try + // to parse it in another format; perhaps the server + // mistakingly identified the playlist format + + [self parsePlaylistM3U:playlistData]; + } + } + + if ([_playlistItems count] == 0) { + /* + * Fail if we failed to parse any items from the playlist. + */ + self.onFailure(); + } +} + +- (void)parsePlaylistM3U:(NSString *)playlist +{ + [_playlistItems removeAllObjects]; + + for (NSString *line in [playlist componentsSeparatedByString:@"\n"]) { + if ([line hasPrefix:@"#"]) { + /* metadata, skip */ + continue; + } + if ([line hasPrefix:@"http://"] || + [line hasPrefix:@"https://"]) { + FSPlaylistItem *item = [[FSPlaylistItem alloc] init]; + item.url = [NSURL URLWithString:[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]]; + + [_playlistItems addObject:item]; + } else if ([line hasPrefix:@"file://"]) { + FSPlaylistItem *item = [[FSPlaylistItem alloc] init]; + item.url = [self parseLocalFileUrl:line]; + + [_playlistItems addObject:item]; + } + } +} + +- (void)parsePlaylistPLS:(NSString *)playlist +{ + [_playlistItems removeAllObjects]; + + NSMutableDictionary *props = [[NSMutableDictionary alloc] init]; + + size_t i = 0; + + for (NSString *rawLine in [playlist componentsSeparatedByString:@"\n"]) { + NSString *line = [rawLine stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if (i == 0) { + if ([[line lowercaseString] hasPrefix:@"[playlist]"]) { + i++; + continue; + } else { + // Invalid playlist; the first line should indicate that this is a playlist + return; + } + } + + // Ignore empty lines + if ([line length] == 0) { + i++; + continue; + } + + // Not an empty line; so expect that this is a key/value pair + NSRange r = [line rangeOfString:@"="]; + + // Invalid format, key/value pair not found + if (r.length == 0) { + return; + } + + NSString *key = [[line substringToIndex:r.location] lowercaseString]; + NSString *value = [line substringFromIndex:r.location + 1]; + + props[key] = value; + i++; + } + + NSInteger numItems = [[props valueForKey:@"numberofentries"] integerValue]; + + if (numItems == 0) { + // Invalid playlist; number of playlist items not defined + return; + } + + for (i=0; i < numItems; i++) { + FSPlaylistItem *item = [[FSPlaylistItem alloc] init]; + + NSString *title = [props valueForKey:[NSString stringWithFormat:@"title%lu", (i+1)]]; + + item.title = title; + + NSString *file = [props valueForKey:[NSString stringWithFormat:@"file%lu", (i+1)]]; + + if ([file hasPrefix:@"http://"] || + [file hasPrefix:@"https://"]) { + item.url = [NSURL URLWithString:file]; + + [_playlistItems addObject:item]; + } else if ([file hasPrefix:@"file://"]) { + item.url = [self parseLocalFileUrl:file]; + + [_playlistItems addObject:item]; + } + } +} + +- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl +{ + // Resolve the local bundle URL + NSString *path = [fileUrl substringFromIndex:7]; + + NSRange range = [path rangeOfString:@"." options:NSBackwardsSearch]; + + NSString *fileName = [path substringWithRange:NSMakeRange(0, range.location)]; + NSString *suffix = [path substringWithRange:NSMakeRange(range.location + 1, [path length] - [fileName length] - 1)]; + + return [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:fileName ofType:suffix]]; +} + +/* + * ======================================= + * NSURLSessionDelegate + * ======================================= + */ + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + _httpStatus = [httpResponse statusCode]; + + NSString *contentType = response.MIMEType; + NSString *absoluteUrl = [response.URL absoluteString]; + + _format = kFSPlaylistFormatNone; + + if ([contentType isEqualToString:@"audio/x-mpegurl"] || + [contentType isEqualToString:@"application/x-mpegurl"]) { + _format = kFSPlaylistFormatM3U; + } else if ([contentType isEqualToString:@"audio/x-scpls"] || + [contentType isEqualToString:@"application/pls+xml"]) { + _format = kFSPlaylistFormatPLS; + } else if ([contentType isEqualToString:@"text/plain"]) { + /* The server did not provide meaningful content type; + last resort: check the file suffix, if there is one */ + + if ([absoluteUrl hasSuffix:@".m3u"]) { + _format = kFSPlaylistFormatM3U; + } else if ([absoluteUrl hasSuffix:@".pls"]) { + _format = kFSPlaylistFormatPLS; + } + } + + if (_format == kFSPlaylistFormatNone) { + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSParsePlaylistRequest: Unable to determine the type of the playlist for URL: %@", _url); +#endif + + self.onFailure(); + + } else { + completionHandler(NSURLSessionResponseAllow); + } + + [_receivedData setLength:0]; +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask +{ + // Resume the Download Task manually because apparently iOS does not do it automatically?! + [downloadTask resume]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + [_receivedData appendData:data]; +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task +didCompleteWithError:(nullable NSError *)error { + if(error) { + @synchronized (self) { + _task = nil; + _receivedData = nil; + } + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSParsePlaylistRequest: Connection failed for URL: %@, error %@", _url, [error localizedDescription]); +#endif + + self.onFailure(); + } else { + @synchronized (self) { + _task = nil; + } + + if (_httpStatus != 200) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSParsePlaylistRequest: Unable to receive playlist from URL: %@", _url); +#endif + + self.onFailure(); + return; + } + + [self parsePlaylistFromData:_receivedData]; + + self.onCompletion(); + } +} + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.h new file mode 100644 index 0000000..4371937 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.h @@ -0,0 +1,28 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import "FSXMLHttpRequest.h" + +/** + * Use this request for retrieving the contents for a podcast RSS feed. + * Upon request completion, the resulting playlist items are + * in the playlistItems property. + * + * See the FSXMLHttpRequest class how to form a request to retrieve + * the RSS feed. + */ +@interface FSParseRssPodcastFeedRequest : FSXMLHttpRequest { + NSMutableArray *_playlistItems; +} + +/** + * The playlist items stored in the FSPlaylistItem class. + */ +@property (readonly) NSMutableArray *playlistItems; + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.m b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.m new file mode 100644 index 0000000..ff49e38 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.m @@ -0,0 +1,100 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import +#import + +#import "FSParseRssPodcastFeedRequest.h" +#import "FSPlaylistItem.h" + +static NSString *const kXPathQueryItems = @"/rss/channel/item"; + +@interface FSParseRssPodcastFeedRequest () +- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl; +- (void)parseItems:(xmlNodePtr)node; +@end + +@implementation FSParseRssPodcastFeedRequest + +- (NSURL *)parseLocalFileUrl:(NSString *)fileUrl +{ + // Resolve the local bundle URL + NSString *path = [fileUrl substringFromIndex:7]; + + NSRange range = [path rangeOfString:@"." options:NSBackwardsSearch]; + + NSString *fileName = [path substringWithRange:NSMakeRange(0, range.location)]; + NSString *suffix = [path substringWithRange:NSMakeRange(range.location + 1, [path length] - [fileName length] - 1)]; + + return [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:fileName ofType:suffix]]; +} + +- (void)parseItems:(xmlNodePtr)node +{ + FSPlaylistItem *item = [[FSPlaylistItem alloc] init]; + + for (xmlNodePtr n = node->children; n != NULL; n = n->next) { + NSString *nodeName = @((const char *)n->name); + if ([nodeName isEqualToString:@"title"]) { + item.title = [self contentForNode:n]; + } else if ([nodeName isEqualToString:@"enclosure"]) { + NSString *url = [self contentForNodeAttribute:n attribute:"url"]; + + if ([url hasPrefix:@"file://"]) { + item.url = [self parseLocalFileUrl:url]; + } else { + item.url = [NSURL URLWithString:url]; + } + } else if ([nodeName isEqualToString:@"link"]) { + NSString *url = [self contentForNode:n]; + + if ([url hasPrefix:@"file://"]) { + item.originatingUrl = [self parseLocalFileUrl:url]; + } else { + item.originatingUrl = [NSURL URLWithString:url]; + } + } + } + + if (nil == item.url && + nil == item.originatingUrl) { + // Not a valid item, as there is no URL. Skip. + return; + } + + [_playlistItems addObject:item]; +} + +- (void)parseResponseData +{ + if (!_playlistItems) { + _playlistItems = [[NSMutableArray alloc] init]; + } + [_playlistItems removeAllObjects]; + + // RSS feed publication date format: + // Sun, 22 Jul 2012 17:35:05 GMT + [_dateFormatter setDateFormat:@"EEE, dd MMMM yyyy HH:mm:ss V"]; + [_dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"]]; + + [self performXPathQuery:kXPathQueryItems]; +} + +- (void)parseXMLNode:(xmlNodePtr)node xPathQuery:(NSString *)xPathQuery +{ + if ([xPathQuery isEqualToString:kXPathQueryItems]) { + [self parseItems:node]; + } +} + +- (NSArray *)playlistItems +{ + return _playlistItems; +} + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.h new file mode 100644 index 0000000..6cd38c4 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.h @@ -0,0 +1,41 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import + +/** + * A playlist item. Each item has a title and url. + */ +@interface FSPlaylistItem : NSObject { +} + +/** + * The title of the playlist item. + */ +@property (nonatomic,copy) NSString *title; +/** + * The URL of the playlist item. + */ +@property (nonatomic,copy) NSURL *url; +/** + * The originating URL of the playlist item. + */ +@property (nonatomic,copy) NSURL *originatingUrl; + +/** + * The number of bytes of audio data. Notice that this may differ + * from the number of bytes the server returns for the content length! + * For instance audio file meta data is excluded from the count. + * Effectively you can use this property for seeking calculations. + * + * The property is only available for non-continuous streams which + * have been in the "playing" state. + */ +@property (nonatomic,assign) UInt64 audioDataByteCount; + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.m b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.m new file mode 100644 index 0000000..f776da0 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSPlaylistItem.m @@ -0,0 +1,25 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import "FSPlaylistItem.h" + +@implementation FSPlaylistItem + +- (BOOL)isEqual:(id)anObject +{ + FSPlaylistItem *otherObject = anObject; + + if ([otherObject.title isEqual:self.title] && + [otherObject.url isEqual:self.url]) { + return YES; + } + + return NO; +} + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.h new file mode 100644 index 0000000..43fcc52 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.h @@ -0,0 +1,112 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import + +typedef struct _xmlDoc xmlDoc; +typedef xmlDoc *xmlDocPtr; + +typedef struct _xmlNode xmlNode; +typedef xmlNode *xmlNodePtr; + +/** + * XML HTTP request error status. + */ +typedef NS_ENUM(NSInteger, FSXMLHttpRequestError) { + /** + * No error. + */ + FSXMLHttpRequestError_NoError = 0, + /** + * Connection failed. + */ + FSXMLHttpRequestError_Connection_Failed, + /** + * Invalid HTTP status. + */ + FSXMLHttpRequestError_Invalid_Http_Status, + /** + * XML parser failed. + */ + FSXMLHttpRequestError_XML_Parser_Failed +}; + +/** + * FSXMLHttpRequest is a class for retrieving data in the XML + * format over a HTTP or HTTPS connection. It provides + * the necessary foundation for parsing the retrieved XML data. + * This class is not meant to be used directly but subclassed + * to a specific requests. + * + * The usage pattern is the following: + * + * 1. Specify the URL with the url property. + * 2. Define the onCompletion and onFailure handlers. + * 3. Call the start method. + */ +@interface FSXMLHttpRequest : NSObject { + NSURLSessionTask *_task; + xmlDocPtr _xmlDocument; + NSDateFormatter *_dateFormatter; +} + +/** + * The URL of the request. + */ +@property (nonatomic,copy) NSURL *url; +/** + * Called upon completion of the request. + */ +@property (copy) void (^onCompletion)(void); +/** + * Called upon a failure. + */ +@property (copy) void (^onFailure)(void); +/** + * If the request fails, contains the latest error status. + */ +@property (readonly) FSXMLHttpRequestError lastError; + +/** + * Starts the request. + */ +- (void)start; +/** + * Cancels the request. + */ +- (void)cancel; + +/** + * Performs an XPath query on the parsed XML data. + * Yields a parseXMLNode method call, which must be + * defined in the subclasses. + * + * @param query The XPath query to be performed. + */ +- (NSArray *)performXPathQuery:(NSString *)query; +/** + * Retrieves content for the given XML node. + * + * @param node The node for content retreval. + */ +- (NSString *)contentForNode:(xmlNodePtr)node; +/** + * Retrieves content for the given XML node attribute. + * + * @param node The node for content retrieval. + * @param attr The attribute from which the content is retrieved. + */ +- (NSString *)contentForNodeAttribute:(xmlNodePtr)node attribute:(const char *)attr; +/** + * Retrieves date from the given XML node. + * + * @param node The node for retrieving the date. + */ +- (NSDate *)dateFromNode:(xmlNodePtr)node; + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.m b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.m new file mode 100644 index 0000000..57c9ab1 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/FSXMLHttpRequest.m @@ -0,0 +1,255 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#import "FSXMLHttpRequest.h" + +#import +#import + +#define DATE_COMPONENTS (NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit | NSWeekdayOrdinalCalendarUnit) +#define CURRENT_CALENDAR [NSCalendar currentCalendar] + +@interface FSXMLHttpRequest (PrivateMethods) +- (const char *)detectEncoding; +- (void)parseResponseData; +- (void)parseXMLNode:(xmlNodePtr)node xPathQuery:(NSString *)xPathQuery; + +@end + +@implementation FSXMLHttpRequest + +- (id)init +{ + self = [super init]; + if (self) { + _dateFormatter = [[NSDateFormatter alloc] init]; + } + return self; +} + +- (void)start +{ + + if (_task) { + return; + } + + _lastError = FSXMLHttpRequestError_NoError; + + NSURLRequest *request = [NSURLRequest requestWithURL:self.url + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:10.0]; + + NSURLSession *session = [NSURLSession sharedSession]; + + __weak FSXMLHttpRequest *weakSelf = self; + + @synchronized (self) { + _task = [session dataTaskWithRequest:request + completionHandler: + ^(NSData *data, NSURLResponse *response, NSError *error) { + FSXMLHttpRequest *strongSelf = weakSelf; + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; + if(error) { + strongSelf->_lastError = FSXMLHttpRequestError_Connection_Failed; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSXMLHttpRequest: Request failed for URL: %@, error %@", strongSelf.url, [error localizedDescription]); +#endif + dispatch_async(dispatch_get_main_queue(), ^(){ + strongSelf.onFailure(); + }); + } else { + if (httpResponse.statusCode != 200) { + strongSelf->_lastError = FSXMLHttpRequestError_Invalid_Http_Status; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSXMLHttpRequest: Unable to receive content for URL: %@", strongSelf.url); +#endif + dispatch_async(dispatch_get_main_queue(), ^(){ + strongSelf.onFailure(); + }); + return; + } + + const char *encoding = [self detectEncoding:data]; + + strongSelf->_xmlDocument = xmlReadMemory([data bytes], + (int)[data length], + "", + encoding, + 0); + + if (!strongSelf->_xmlDocument) { + strongSelf->_lastError = FSXMLHttpRequestError_XML_Parser_Failed; + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + NSLog(@"FSXMLHttpRequest: Unable to parse the content for URL: %@", strongSelf.url); +#endif + + dispatch_async(dispatch_get_main_queue(), ^(){ + strongSelf.onFailure(); + }); + return; + } + + [strongSelf parseResponseData]; + + xmlFreeDoc(strongSelf->_xmlDocument); + strongSelf->_xmlDocument = nil; + + dispatch_async(dispatch_get_main_queue(), ^(){ + strongSelf.onCompletion(); + }); + } + }]; + } + [_task resume]; + +} + +- (void)cancel +{ + if (!_task) { + return; + } + @synchronized (self) { + [_task cancel]; + _task = nil; + } +} + +/* + * ======================================= + * XML handling + * ======================================= + */ + +- (NSArray *)performXPathQuery:(NSString *)query +{ + NSMutableArray *resultNodes = [NSMutableArray array]; + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + + xpathCtx = xmlXPathNewContext(_xmlDocument); + if (xpathCtx == NULL) { + goto cleanup; + } + + xpathObj = xmlXPathEvalExpression((xmlChar *)[query cStringUsingEncoding:NSUTF8StringEncoding], xpathCtx); + if (xpathObj == NULL) { + goto cleanup; + } + + xmlNodeSetPtr nodes = xpathObj->nodesetval; + if (!nodes) { + goto cleanup; + } + + for (size_t i = 0; i < nodes->nodeNr; i++) { + [self parseXMLNode:nodes->nodeTab[i] xPathQuery:query]; + } + +cleanup: + if (xpathObj) { + xmlXPathFreeObject(xpathObj); + } + if (xpathCtx) { + xmlXPathFreeContext(xpathCtx); + } + return resultNodes; +} + +- (NSString *)contentForNode:(xmlNodePtr)node +{ + NSString *stringWithContent; + if (!node) { + stringWithContent = [[NSString alloc] init]; + } else { + xmlChar *content = xmlNodeGetContent(node); + if (!content) { + return stringWithContent; + } + stringWithContent = @((const char *)content); + xmlFree(content); + } + return stringWithContent; +} + +- (NSString *)contentForNodeAttribute:(xmlNodePtr)node attribute:(const char *)attr +{ + NSString *stringWithContent; + if (!node) { + stringWithContent = [[NSString alloc] init]; + } else { + xmlChar *content = xmlGetProp(node, (const xmlChar *)attr); + if (!content) { + return stringWithContent; + } + stringWithContent = @((const char *)content); + xmlFree(content); + } + return stringWithContent; +} + +/* + * ======================================= + * Helpers + * ======================================= + */ + +- (const char *)detectEncoding:(NSData *)receivedData +{ + const char *encoding = 0; + const char *header = strndup([receivedData bytes], 60); + + if (strstr(header, "utf-8") || strstr(header, "UTF-8")) { + encoding = "UTF-8"; + } else if (strstr(header, "iso-8859-1") || strstr(header, "ISO-8859-1")) { + encoding = "ISO-8859-1"; + } + + free((void *)header); + return encoding; +} + +- (NSDate *)dateFromNode:(xmlNodePtr)node +{ + NSString *dateString = [self contentForNode:node]; + + /* + * For some NSDateFormatter date parsing oddities: http://www.openradar.me/9944011 + * + * Engineering has determined that this issue behaves as intended based on the following information: + * + * This is an intentional change in iOS 5. The issue is this: With the short formats as specified by z (=zzz) or v (=vvv), + * there can be a lot of ambiguity. For example, "ET" for Eastern Time" could apply to different time zones in many different regions. + * To improve formatting and parsing reliability, the short forms are only used in a locale if the "cu" (commonly used) flag is set + * for the locale. Otherwise, only the long forms are used (for both formatting and parsing). This is a change in + * open-source CLDR 2.0 / ICU 4.8, which is the basis for the ICU in iOS 5, which in turn is the basis of NSDateFormatter behavior. + * + * For the "en" locale (= "en_US"), the cu flag is set for metazones such as Alaska, America_Central, America_Eastern, America_Mountain, + * America_Pacific, Atlantic, Hawaii_Aleutian, and GMT. It is not set for Europe_Central. + * + * However, for the "en_GB" locale, the cu flag is set for Europe_Central. + * + * So, a formatter set for short timezone style "z" or "zzz" and locale "en" or "en_US" will not parse "CEST" or "CET", but if the + * locale is instead set to "en_GB" it will parse those. The "GMT" style will be parsed by all. + * + * If the formatter is set for the long timezone style "zzzz", and the locale is any of "en", "en_US", or "en_GB", then any of the + * following will be parsed, because they are unambiguous: + * + * "Pacific Daylight Time" "Central European Summer Time" "Central European Time" + * + */ + + return [_dateFormatter dateFromString:dateString]; +} + +@end diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.cpp new file mode 100644 index 0000000..e5d1929 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.cpp @@ -0,0 +1,566 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "audio_queue.h" +#include "stream_configuration.h" + +#include + +//#define AQ_DEBUG 1 +//#define AQ_DEBUG_LOCKS 1 + +#if !defined (AQ_DEBUG) + #define AQ_TRACE(...) do {} while (0) + #define AQ_ASSERT(...) do {} while (0) +#else + #include + + #define AQ_TRACE(...) printf(__VA_ARGS__) + #define AQ_ASSERT(...) assert(__VA_ARGS__) +#endif + +#if !defined (AQ_DEBUG_LOCKS) +#define AQ_LOCK_TRACE(...) do {} while (0) +#else +#define AQ_LOCK_TRACE(...) printf(__VA_ARGS__) +#endif + + +namespace astreamer { + +/* public */ + +Audio_Queue::Audio_Queue() + : m_delegate(0), + m_state(IDLE), + m_outAQ(0), + m_fillBufferIndex(0), + m_bytesFilled(0), + m_packetsFilled(0), + m_buffersUsed(0), + m_audioQueueStarted(false), + m_levelMeteringEnabled(false), + m_lastError(noErr), + m_initialOutputVolume(1.0) +{ + Stream_Configuration *config = Stream_Configuration::configuration(); + + m_audioQueueBuffer = new AudioQueueBufferRef[config->bufferCount]; + m_packetDescs = new AudioStreamPacketDescription[config->maxPacketDescs]; + m_bufferInUse = new bool[config->bufferCount]; + + for (size_t i=0; i < config->bufferCount; i++) { + m_bufferInUse[i] = false; + } + + if (pthread_mutex_init(&m_mutex, NULL) != 0) { + AQ_TRACE("m_mutex init failed!\n"); + } + + if (pthread_mutex_init(&m_bufferInUseMutex, NULL) != 0) { + AQ_TRACE("m_bufferInUseMutex init failed!\n"); + } + + if (pthread_cond_init(&m_bufferFreeCondition, NULL) != 0) { + AQ_TRACE("m_bufferFreeCondition init failed!\n"); + } +} + +Audio_Queue::~Audio_Queue() +{ + stop(true); + + cleanup(); + + delete [] m_audioQueueBuffer; + delete [] m_packetDescs; + delete [] m_bufferInUse; + + pthread_mutex_destroy(&m_mutex); + pthread_mutex_destroy(&m_bufferInUseMutex); + pthread_cond_destroy(&m_bufferFreeCondition); +} + +bool Audio_Queue::initialized() +{ + return (m_outAQ != 0); +} + +void Audio_Queue::start() +{ + // start the queue if it has not been started already + if (m_audioQueueStarted) { + return; + } + + OSStatus err = AudioQueueStart(m_outAQ, NULL); + if (!err) { + m_audioQueueStarted = true; + m_levelMeteringEnabled = false; + m_lastError = noErr; + } else { + AQ_TRACE("%s: AudioQueueStart failed!\n", __PRETTY_FUNCTION__); + m_lastError = err; + } +} + +void Audio_Queue::pause() +{ + if (m_state == RUNNING) { + if (AudioQueuePause(m_outAQ) != 0) { + AQ_TRACE("%s: AudioQueuePause failed!\n", __PRETTY_FUNCTION__); + } + setState(PAUSED); + } else if (m_state == PAUSED) { + AudioQueueStart(m_outAQ, NULL); + setState(RUNNING); + } +} + +void Audio_Queue::stop() +{ + stop(true); +} + +float Audio_Queue::volume() +{ + if (!m_outAQ) { + return 1.0; + } + + float vol; + + OSStatus err = AudioQueueGetParameter(m_outAQ, kAudioQueueParam_Volume, &vol); + + if (!err) { + return vol; + } + + return 1.0; +} + +void Audio_Queue::setVolume(float volume) +{ + if (!m_outAQ) { + return; + } + AudioQueueSetParameter(m_outAQ, kAudioQueueParam_Volume, volume); +} + +void Audio_Queue::setPlayRate(float playRate) +{ + Stream_Configuration *configuration = Stream_Configuration::configuration(); + + if (!configuration->enableTimeAndPitchConversion) { +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + printf("*** FreeStreamer notification: Trying to set play rate for audio queue but enableTimeAndPitchConversion is disabled from configuration. Play rate settign will not work.\n"); +#endif + return; + } + + if (!m_outAQ) { + return; + } + + if (playRate < 0.5) { + playRate = 0.5; + } + if (playRate > 2.0) { + playRate = 2.0; + } + + AudioQueueSetParameter(m_outAQ, kAudioQueueParam_PlayRate, playRate); +} + +void Audio_Queue::stop(bool stopImmediately) +{ + if (!m_audioQueueStarted) { + AQ_TRACE("%s: audio queue already stopped, return!\n", __PRETTY_FUNCTION__); + return; + } + m_audioQueueStarted = false; + m_levelMeteringEnabled = false; + + pthread_mutex_lock(&m_bufferInUseMutex); + pthread_cond_signal(&m_bufferFreeCondition); + pthread_mutex_unlock(&m_bufferInUseMutex); + + AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__); + + if (AudioQueueFlush(m_outAQ) != 0) { + AQ_TRACE("%s: AudioQueueFlush failed!\n", __PRETTY_FUNCTION__); + } + + if (stopImmediately) { + AudioQueueRemovePropertyListener(m_outAQ, + kAudioQueueProperty_IsRunning, + audioQueueIsRunningCallback, + this); + } + + if (AudioQueueStop(m_outAQ, stopImmediately) != 0) { + AQ_TRACE("%s: AudioQueueStop failed!\n", __PRETTY_FUNCTION__); + } + + if (stopImmediately) { + setState(IDLE); + } + + AQ_TRACE("%s: leave\n", __PRETTY_FUNCTION__); +} + +AudioTimeStamp Audio_Queue::currentTime() +{ + AudioTimeStamp queueTime; + Boolean discontinuity; + + memset(&queueTime, 0, sizeof queueTime); + + OSStatus err = AudioQueueGetCurrentTime(m_outAQ, NULL, &queueTime, &discontinuity); + if (err) { + AQ_TRACE("AudioQueueGetCurrentTime failed\n"); + } + + return queueTime; +} + +AudioQueueLevelMeterState Audio_Queue::levels() +{ + if (!m_levelMeteringEnabled) { + UInt32 enabledLevelMeter = true; + AudioQueueSetProperty(m_outAQ, + kAudioQueueProperty_EnableLevelMetering, + &enabledLevelMeter, + sizeof(UInt32)); + + m_levelMeteringEnabled = true; + } + + AudioQueueLevelMeterState levelMeter; + UInt32 levelMeterSize = sizeof(AudioQueueLevelMeterState); + AudioQueueGetProperty(m_outAQ, kAudioQueueProperty_CurrentLevelMeterDB, &levelMeter, &levelMeterSize); + return levelMeter; +} + +void Audio_Queue::init() +{ + OSStatus err = noErr; + + cleanup(); + + // create the audio queue + err = AudioQueueNewOutput(&m_streamDesc, audioQueueOutputCallback, this, CFRunLoopGetCurrent(), NULL, 0, &m_outAQ); + if (err) { + AQ_TRACE("%s: error in AudioQueueNewOutput\n", __PRETTY_FUNCTION__); + + m_lastError = err; + + if (m_delegate) { + m_delegate->audioQueueInitializationFailed(); + } + + return; + } + + Stream_Configuration *configuration = Stream_Configuration::configuration(); + + // allocate audio queue buffers + for (unsigned int i = 0; i < configuration->bufferCount; ++i) { + err = AudioQueueAllocateBuffer(m_outAQ, configuration->bufferSize, &m_audioQueueBuffer[i]); + if (err) { + /* If allocating the buffers failed, everything else will fail, too. + * Dispose the queue so that we can later on detect that this + * queue in fact has not been initialized. + */ + + AQ_TRACE("%s: error in AudioQueueAllocateBuffer\n", __PRETTY_FUNCTION__); + + (void)AudioQueueDispose(m_outAQ, true); + m_outAQ = 0; + + m_lastError = err; + + if (m_delegate) { + m_delegate->audioQueueInitializationFailed(); + } + + return; + } + } + + // listen for kAudioQueueProperty_IsRunning + err = AudioQueueAddPropertyListener(m_outAQ, kAudioQueueProperty_IsRunning, audioQueueIsRunningCallback, this); + if (err) { + AQ_TRACE("%s: error in AudioQueueAddPropertyListener\n", __PRETTY_FUNCTION__); + m_lastError = err; + return; + } + + if (configuration->enableTimeAndPitchConversion) { + UInt32 enableTimePitchConversion = 1; + + err = AudioQueueSetProperty (m_outAQ, kAudioQueueProperty_EnableTimePitch, &enableTimePitchConversion, sizeof(enableTimePitchConversion)); + if (err != noErr) { + AQ_TRACE("Failed to enable time and pitch conversion. Play rate setting will fail\n"); + } + } + + if (m_initialOutputVolume != 1.0) { + setVolume(m_initialOutputVolume); + } +} + +void Audio_Queue::handleAudioPackets(UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions) +{ + if (!initialized()) { + AQ_TRACE("%s: warning: attempt to handle audio packets with uninitialized audio queue. return.\n", __PRETTY_FUNCTION__); + + return; + } + + // this is called by audio file stream when it finds packets of audio + AQ_TRACE("got data. bytes: %u packets: %u\n", inNumberBytes, (unsigned int)inNumberPackets); + + /* Place each packet into a buffer and then send each buffer into the audio + queue */ + UInt32 i; + + for (i = 0; i < inNumberPackets; i++) { + AudioStreamPacketDescription *desc = &inPacketDescriptions[i]; + + const void *data = (const char*)inInputData + desc->mStartOffset; + + if (!initialized()) { + AQ_TRACE("%s: warning: attempt to handle audio packets with uninitialized audio queue. return.\n", __PRETTY_FUNCTION__); + + return; + } + + Stream_Configuration *config = Stream_Configuration::configuration(); + + AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__); + + UInt32 packetSize = desc->mDataByteSize; + + /* This shouldn't happen because most of the time we read the packet buffer + size from the file stream, but if we restored to guessing it we could + come up too small here */ + if (packetSize > config->bufferSize) { + AQ_TRACE("%s: packetSize %u > AQ_BUFSIZ %li\n", __PRETTY_FUNCTION__, (unsigned int)packetSize, config->bufferSize); + return; + } + + // if the space remaining in the buffer is not enough for this packet, then + // enqueue the buffer and wait for another to become available. + if (config->bufferSize - m_bytesFilled < packetSize) { + enqueueBuffer(); + + if (!m_audioQueueStarted) { + return; + } + } else { + AQ_TRACE("%s: skipped enqueueBuffer AQ_BUFSIZ - m_bytesFilled %lu, packetSize %u\n", __PRETTY_FUNCTION__, (config->bufferSize - m_bytesFilled), (unsigned int)packetSize); + } + + // copy data to the audio queue buffer + AudioQueueBufferRef buf = m_audioQueueBuffer[m_fillBufferIndex]; + memcpy((char*)buf->mAudioData, data, packetSize); + + // fill out packet description to pass to enqueue() later on + m_packetDescs[m_packetsFilled] = *desc; + // Make sure the offset is relative to the start of the audio buffer + m_packetDescs[m_packetsFilled].mStartOffset = m_bytesFilled; + // keep track of bytes filled and packets filled + m_bytesFilled += packetSize; + m_packetsFilled++; + + /* If filled our buffer with packets, then commit it to the system */ + if (m_packetsFilled >= config->maxPacketDescs) { + enqueueBuffer(); + } + } +} + +/* private */ + +void Audio_Queue::cleanup() +{ + if (!initialized()) { + AQ_TRACE("%s: warning: attempt to cleanup an uninitialized audio queue. return.\n", __PRETTY_FUNCTION__); + + return; + } + + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (m_state != IDLE) { + AQ_TRACE("%s: attemping to cleanup the audio queue when it is still playing, force stopping\n", + __PRETTY_FUNCTION__); + + AudioQueueRemovePropertyListener(m_outAQ, + kAudioQueueProperty_IsRunning, + audioQueueIsRunningCallback, + this); + + AudioQueueStop(m_outAQ, true); + setState(IDLE); + } + + if (AudioQueueDispose(m_outAQ, true) != 0) { + AQ_TRACE("%s: AudioQueueDispose failed!\n", __PRETTY_FUNCTION__); + } + m_outAQ = 0; + m_fillBufferIndex = m_bytesFilled = m_packetsFilled = m_buffersUsed = 0; + + for (size_t i=0; i < config->bufferCount; i++) { + m_bufferInUse[i] = false; + } + + m_lastError = noErr; +} + +void Audio_Queue::setState(State state) +{ + if (m_state == state) { + /* We are already in this state! */ + return; + } + + m_state = state; + + if (m_delegate) { + m_delegate->audioQueueStateChanged(state); + } +} + +void Audio_Queue::enqueueBuffer() +{ + AQ_ASSERT(!m_bufferInUse[m_fillBufferIndex]); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__); + + pthread_mutex_lock(&m_bufferInUseMutex); + + m_bufferInUse[m_fillBufferIndex] = true; + m_buffersUsed++; + + // enqueue buffer + AudioQueueBufferRef fillBuf = m_audioQueueBuffer[m_fillBufferIndex]; + fillBuf->mAudioDataByteSize = m_bytesFilled; + + pthread_mutex_unlock(&m_bufferInUseMutex); + + AQ_ASSERT(m_packetsFilled > 0); + OSStatus err = AudioQueueEnqueueBuffer(m_outAQ, fillBuf, m_packetsFilled, m_packetDescs); + + if (!err) { + m_lastError = noErr; + start(); + } else { + /* If we get an error here, it very likely means that the audio queue is no longer + running */ + AQ_TRACE("%s: error in AudioQueueEnqueueBuffer\n", __PRETTY_FUNCTION__); + m_lastError = err; + return; + } + + pthread_mutex_lock(&m_bufferInUseMutex); + // go to next buffer + if (++m_fillBufferIndex >= config->bufferCount) { + m_fillBufferIndex = 0; + } + // reset bytes filled + m_bytesFilled = 0; + // reset packets filled + m_packetsFilled = 0; + + // wait until next buffer is not in use + + while (m_bufferInUse[m_fillBufferIndex]) { + AQ_TRACE("waiting for buffer %u\n", (unsigned int)m_fillBufferIndex); + + pthread_cond_wait(&m_bufferFreeCondition, &m_bufferInUseMutex); + } + pthread_mutex_unlock(&m_bufferInUseMutex); +} + +// this is called by the audio queue when it has finished decoding our data. +// The buffer is now free to be reused. +void Audio_Queue::audioQueueOutputCallback(void *inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) +{ + Audio_Queue *audioQueue = static_cast(inClientData); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + int bufIndex = -1; + + for (unsigned int i = 0; i < config->bufferCount; ++i) { + if (inBuffer == audioQueue->m_audioQueueBuffer[i]) { + AQ_TRACE("findQueueBuffer %i\n", i); + bufIndex = i; + break; + } + } + + if (bufIndex == -1) { + return; + } + + pthread_mutex_lock(&audioQueue->m_bufferInUseMutex); + + AQ_ASSERT(audioQueue->m_bufferInUse[bufIndex]); + + audioQueue->m_bufferInUse[bufIndex] = false; + audioQueue->m_buffersUsed--; + + AQ_TRACE("signaling buffer free for inuse %i....\n", bufIndex); + pthread_cond_signal(&audioQueue->m_bufferFreeCondition); + AQ_TRACE("signal sent!\n"); + + if (audioQueue->m_buffersUsed == 0 && audioQueue->m_delegate) { + AQ_LOCK_TRACE("audioQueueOutputCallback: unlock 2\n"); + pthread_mutex_unlock(&audioQueue->m_bufferInUseMutex); + + if (audioQueue->m_delegate) { + audioQueue->m_delegate->audioQueueBuffersEmpty(); + } + } else { + pthread_mutex_unlock(&audioQueue->m_bufferInUseMutex); + + if (audioQueue->m_delegate) { + audioQueue->m_delegate->audioQueueFinishedPlayingPacket(); + } + } + + AQ_LOCK_TRACE("audioQueueOutputCallback: unlock\n"); +} + +void Audio_Queue::audioQueueIsRunningCallback(void *inClientData, AudioQueueRef inAQ, AudioQueuePropertyID inID) +{ + Audio_Queue *audioQueue = static_cast(inClientData); + + AQ_TRACE("%s: enter\n", __PRETTY_FUNCTION__); + + UInt32 running; + UInt32 output = sizeof(running); + OSStatus err = AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &running, &output); + if (err) { + AQ_TRACE("%s: error in kAudioQueueProperty_IsRunning\n", __PRETTY_FUNCTION__); + return; + } + if (running) { + AQ_TRACE("audio queue running!\n"); + audioQueue->setState(RUNNING); + } else { + audioQueue->setState(IDLE); + } +} + +} // namespace astreamer diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.h new file mode 100644 index 0000000..59a4fdf --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_queue.h @@ -0,0 +1,102 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_AUDIO_QUEUE_H +#define ASTREAMER_AUDIO_QUEUE_H + +#include /* AudioFileStreamID */ + +namespace astreamer { + +class Audio_Queue_Delegate; +struct queued_packet; + +class Audio_Queue { +public: + Audio_Queue_Delegate *m_delegate; + + enum State { + IDLE, + RUNNING, + PAUSED + }; + + Audio_Queue(); + virtual ~Audio_Queue(); + + bool initialized(); + + void init(); + + // Notice: the queue blocks if it has no free buffers + void handleAudioPackets(UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions); + + void start(); + void pause(); + void stop(bool stopImmediately); + void stop(); + + float volume(); + + void setVolume(float volume); + void setPlayRate(float playRate); + + AudioTimeStamp currentTime(); + AudioQueueLevelMeterState levels(); + +private: + Audio_Queue(const Audio_Queue&); + Audio_Queue& operator=(const Audio_Queue&); + + State m_state; + + AudioQueueRef m_outAQ; // the audio queue + + AudioQueueBufferRef *m_audioQueueBuffer; // audio queue buffers + AudioStreamPacketDescription *m_packetDescs; // packet descriptions for enqueuing audio + + UInt32 m_fillBufferIndex; // the index of the audioQueueBuffer that is being filled + UInt32 m_bytesFilled; // how many bytes have been filled + UInt32 m_packetsFilled; // how many packets have been filled + UInt32 m_buffersUsed; // how many buffers are used + + bool m_audioQueueStarted; // flag to indicate that the queue has been started + bool *m_bufferInUse; // flags to indicate that a buffer is still in use + bool m_levelMeteringEnabled; + + pthread_mutex_t m_mutex; + + pthread_mutex_t m_bufferInUseMutex; + pthread_cond_t m_bufferFreeCondition; + +public: + OSStatus m_lastError; + AudioStreamBasicDescription m_streamDesc; + float m_initialOutputVolume; + +private: + void cleanup(); + void setCookiesForStream(AudioFileStreamID inAudioFileStream); + void setState(State state); + void enqueueBuffer(); + + static void audioQueueOutputCallback(void *inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer); + static void audioQueueIsRunningCallback(void *inClientData, AudioQueueRef inAQ, AudioQueuePropertyID inID); +}; + +class Audio_Queue_Delegate { +public: + virtual void audioQueueStateChanged(Audio_Queue::State state) = 0; + virtual void audioQueueBuffersEmpty() = 0; + virtual void audioQueueInitializationFailed() = 0; + virtual void audioQueueFinishedPlayingPacket() = 0; +}; + +} // namespace astreamer + +#endif // ASTREAMER_AUDIO_QUEUE_H diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.cpp new file mode 100644 index 0000000..bd3472b --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.cpp @@ -0,0 +1,2229 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "audio_stream.h" +#include "file_output.h" +#include "stream_configuration.h" +#include "http_stream.h" +#include "file_stream.h" +#include "caching_stream.h" + +#include +#include + +/* + * Some servers may send an incorrect MIME type for the audio stream. + * By uncommenting the following line, relaxed checks will be + * performed for the MIME type. This allows playing more + * streams: + */ +//#define AS_RELAX_CONTENT_TYPE_CHECK 1 + +//#define AS_DEBUG 1 +//#define AS_LOCK_DEBUG 1 + +#if !defined (AS_DEBUG) +#define AS_TRACE(...) do {} while (0) +#else +#define AS_TRACE(...) printf("[audio_stream.cpp:%i thread %x] ", __LINE__, pthread_mach_thread_np(pthread_self())); printf(__VA_ARGS__) +#endif + +#if !defined (AS_LOCK_DEBUG) +#define AS_LOCK_TRACE(...) do {} while (0) +#else +#define AS_LOCK_TRACE(...) printf("[audio_stream.cpp:%i thread %x] ", __LINE__, pthread_mach_thread_np(pthread_self())); printf(__VA_ARGS__) +#endif + +#if defined(DEBUG) || (TARGET_IPHONE_SIMULATOR) + #define AS_WARN(...) printf("[audio_stream.cpp:%i thread %x] ", __LINE__, pthread_mach_thread_np(pthread_self())); printf(__VA_ARGS__) +#else + #define AS_WARN(...) do {} while (0) +#endif + +namespace astreamer { + +static void fsTick(CFRunLoopTimerRef timer, void *info); + +static void fsTick(CFRunLoopTimerRef timer, void *info) +{ + // Dummy function just to keep the decoder runloop running +} + +static CFStringRef coreAudioErrorToCFString(CFStringRef basicErrorDescription, OSStatus error) +{ + char str[20] = {0}; + + *(UInt32 *) (str + 1) = CFSwapInt32HostToBig(error); + if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { + str[0] = str[5] = '\''; + str[6] = '\0'; + } else { + sprintf(str, "%d", (int)error); + } + + CFStringRef formattedError = CFStringCreateWithFormat(NULL, + NULL, + CFSTR("%@: error code %s"), + basicErrorDescription, + str); + return formattedError; +} + +/* Create HTTP stream as Audio_Stream (this) as the delegate */ +Audio_Stream::Audio_Stream() : + m_delegate(0), + m_inputStreamRunning(false), + m_audioStreamParserRunning(false), + m_initialBufferingCompleted(false), + m_discontinuity(false), + m_preloading(false), + m_audioQueueConsumedPackets(false), + m_contentLength(0), + m_defaultContentLength(0), + m_bytesReceived(0), + m_state(STOPPED), + m_inputStream(0), + m_audioQueue(0), + m_watchdogTimer(0), + m_seekTimer(0), + m_inputStreamTimer(0), + m_stateSetTimer(0), + m_decodeTimer(0), + m_audioFileStream(0), + m_audioConverter(0), + m_initializationError(noErr), + m_outputBufferSize(Stream_Configuration::configuration()->bufferSize), + m_outputBuffer(new UInt8[m_outputBufferSize]), + m_packetIdentifier(0), + m_playingPacketIdentifier(0), + m_dataOffset(0), + m_seekOffset(0), + m_bounceCount(0), + m_firstBufferingTime(0), + m_strictContentTypeChecking(Stream_Configuration::configuration()->requireStrictContentTypeChecking), + m_defaultContentType(CFSTR("audio/mpeg")), + m_contentType(NULL), + + m_fileOutput(0), + m_outputFile(NULL), + m_queuedHead(0), + m_queuedTail(0), + m_playPacket(0), + m_cachedDataSize(0), + m_numPacketsToRewind(0), + m_audioDataByteCount(0), + m_audioDataPacketCount(0), + m_bitRate(0), + m_metaDataSizeInBytes(0), + m_packetDuration(0), + m_bitrateBufferIndex(0), + m_outputVolume(1.0), + m_converterRunOutOfData(false), + m_decoderShouldRun(false), + m_decoderFailed(false), + m_decoderThreadCreated(false), + m_mainRunLoop(CFRunLoopGetCurrent()), + m_decodeRunLoop(NULL) +{ + memset(&m_srcFormat, 0, sizeof m_srcFormat); + + memset(&m_dstFormat, 0, sizeof m_dstFormat); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + m_dstFormat.mSampleRate = config->outputSampleRate; + m_dstFormat.mFormatID = kAudioFormatLinearPCM; + m_dstFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + m_dstFormat.mBytesPerPacket = 4; + m_dstFormat.mFramesPerPacket = 1; + m_dstFormat.mBytesPerFrame = 4; + m_dstFormat.mChannelsPerFrame = 2; + m_dstFormat.mBitsPerChannel = 16; + + if (pthread_mutex_init(&m_packetQueueMutex, NULL) != 0) { + AS_TRACE("m_packetQueueMutex init failed!\n"); + } + if (pthread_mutex_init(&m_streamStateMutex, NULL) != 0) { + AS_TRACE("m_streamStateMutex init failed!\n"); + } + + m_decoderThreadCreated = (pthread_create(&m_decodeThread, NULL, decodeLoop, this) == 0); +} + +Audio_Stream::~Audio_Stream() +{ + setDecoderRunState(false); + + if (m_decoderThreadCreated) { + while (m_decodeRunLoop == NULL || !CFRunLoopIsWaiting(m_decodeRunLoop)) { + usleep(0); + } + CFRunLoopStop(m_decodeRunLoop); + pthread_join(m_decodeThread, NULL); + m_decodeRunLoop = NULL; + m_decoderThreadCreated = false; + } + + if (m_defaultContentType) { + CFRelease(m_defaultContentType); + m_defaultContentType = NULL; + } + + if (m_contentType) { + CFRelease(m_contentType); + m_contentType = NULL; + } + + close(true); + + delete [] m_outputBuffer; + m_outputBuffer = 0; + + if (m_inputStream) { + m_inputStream->m_delegate = 0; + delete m_inputStream; + m_inputStream = 0; + } + + if (m_fileOutput) { + delete m_fileOutput; + m_fileOutput = 0; + } + + pthread_mutex_destroy(&m_packetQueueMutex); + pthread_mutex_destroy(&m_streamStateMutex); +} + +void Audio_Stream::open() +{ + open(0); +} + +void Audio_Stream::open(Input_Stream_Position *position) +{ + if (m_inputStreamRunning || m_audioStreamParserRunning) { + AS_TRACE("%s: already running: return\n", __PRETTY_FUNCTION__); + return; + } + + m_contentLength = 0; + m_bytesReceived = 0; + m_seekOffset = 0; + m_bounceCount = 0; + m_firstBufferingTime = 0; + m_bitrateBufferIndex = 0; + m_initializationError = noErr; + m_converterRunOutOfData = false; + m_audioDataPacketCount = 0; + m_bitRate = 0; + m_metaDataSizeInBytes = 0; + m_discontinuity = true; + + setDecoderRunState(false); + + pthread_mutex_lock(&m_streamStateMutex); + m_audioQueueConsumedPackets = false; + m_decoderFailed = false; + pthread_mutex_unlock(&m_streamStateMutex); + + pthread_mutex_lock(&m_packetQueueMutex); + m_numPacketsToRewind = 0; + pthread_mutex_unlock(&m_packetQueueMutex); + + invalidateWatchdogTimer(); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (m_contentType) { + CFRelease(m_contentType); + m_contentType = NULL; + } + + bool success = false; + + if (position) { + m_initialBufferingCompleted = false; + + if (m_inputStream) { + success = m_inputStream->open(*position); + } + } else { + m_initialBufferingCompleted = false; + + m_packetIdentifier = 0; + + if (m_inputStream) { + success = m_inputStream->open(); + } + } + + if (success) { + AS_TRACE("%s: HTTP stream opened, buffering...\n", __PRETTY_FUNCTION__); + m_inputStreamRunning = true; + + setState(BUFFERING); + + pthread_mutex_lock(&m_streamStateMutex); + if (!m_preloading && config->startupWatchdogPeriod > 0) { + pthread_mutex_unlock(&m_streamStateMutex); + + createWatchdogTimer(); + } else { + pthread_mutex_unlock(&m_streamStateMutex); + } + } else { + closeAndSignalError(AS_ERR_OPEN, CFSTR("Input stream open error")); + } +} + +void Audio_Stream::close(bool closeParser) +{ + AS_TRACE("%s: enter\n", __PRETTY_FUNCTION__); + + invalidateWatchdogTimer(); + + if (m_seekTimer) { + CFRunLoopTimerInvalidate(m_seekTimer); + CFRelease(m_seekTimer); + m_seekTimer = 0; + } + + if (m_inputStreamTimer) { + CFRunLoopTimerInvalidate(m_inputStreamTimer); + CFRelease(m_inputStreamTimer); + m_inputStreamTimer = 0; + } + + pthread_mutex_lock(&m_streamStateMutex); + + if (m_stateSetTimer) { + CFRunLoopTimerInvalidate(m_stateSetTimer); + CFRelease(m_stateSetTimer); + m_stateSetTimer = 0; + } + + pthread_mutex_unlock(&m_streamStateMutex); + + /* Close the HTTP stream first so that the audio stream parser + isn't fed with more data to parse */ + if (m_inputStreamRunning) { + if (m_inputStream) { + m_inputStream->close(); + } + m_inputStreamRunning = false; + } + + if (closeParser && m_audioStreamParserRunning) { + if (m_audioFileStream) { + if (AudioFileStreamClose(m_audioFileStream) != 0) { + AS_TRACE("%s: AudioFileStreamClose failed\n", __PRETTY_FUNCTION__); + } + m_audioFileStream = 0; + } + m_audioStreamParserRunning = false; + } + + setDecoderRunState(false); + + pthread_mutex_lock(&m_packetQueueMutex); + m_playPacket = 0; + pthread_mutex_unlock(&m_packetQueueMutex); + + closeAudioQueue(); + + const State currentState = state(); + + if (FAILED != currentState && SEEKING != currentState) { + /* + * Set the stream state to stopped if the stream was stopped successfully. + * We don't want to cause a spurious stopped state as the fail state should + * be the final state in case the stream failed. + */ + setState(STOPPED); + } + + if (m_audioConverter) { + AudioConverterDispose(m_audioConverter); + m_audioConverter = 0; + } + + /* + * Free any remaining queud packets for encoding. + */ + pthread_mutex_lock(&m_packetQueueMutex); + + queued_packet_t *cur = m_queuedHead; + while (cur) { + queued_packet_t *tmp = cur->next; + free(cur); + cur = tmp; + } + m_queuedHead = 0; + m_queuedTail = 0; + m_cachedDataSize = 0; + m_numPacketsToRewind = 0; + + m_processedPackets.clear(); + + pthread_mutex_unlock(&m_packetQueueMutex); + + AS_TRACE("%s: leave\n", __PRETTY_FUNCTION__); +} + +void Audio_Stream::pause() +{ + audioQueue()->pause(); +} + +void Audio_Stream::rewind(unsigned seconds) +{ + const bool continuous = (!(contentLength() > 0)); + + if (!continuous) { + return; + } + + const int packetCount = cachedDataCount(); + + if (packetCount == 0) { + return; + } + + const Float64 averagePacketSize = (Float64)cachedDataSize() / (Float64)packetCount; + const Float64 bufferSizeForSecond = bitrate() / 8.0; + const Float64 totalAudioRequiredInBytes = seconds * bufferSizeForSecond; + + const int packetsToRewind = totalAudioRequiredInBytes / averagePacketSize; + + if (packetCount - packetsToRewind >= 16) { + // Leave some safety margin so that the stream doesn't immediately start buffering + + pthread_mutex_lock(&m_packetQueueMutex); + m_numPacketsToRewind = packetsToRewind; + pthread_mutex_unlock(&m_packetQueueMutex); + } +} + +void Audio_Stream::startCachedDataPlayback() +{ + pthread_mutex_lock(&m_streamStateMutex); + m_preloading = false; + pthread_mutex_unlock(&m_streamStateMutex); + + if (!m_inputStreamRunning) { + // Already reached EOF, restart + open(); + } else { + determineBufferingLimits(); + } +} + +AS_Playback_Position Audio_Stream::playbackPosition() +{ + AS_Playback_Position playbackPosition; + playbackPosition.offset = 0; + playbackPosition.timePlayed = 0; + + if (m_audioStreamParserRunning) { + AudioTimeStamp queueTime = audioQueue()->currentTime(); + + playbackPosition.timePlayed = (durationInSeconds() * m_seekOffset) + + (queueTime.mSampleTime / m_dstFormat.mSampleRate); + + float duration = durationInSeconds(); + + if (duration > 0) { + playbackPosition.offset = playbackPosition.timePlayed / durationInSeconds(); + } + } + return playbackPosition; +} + +UInt64 Audio_Stream::audioDataByteCount() +{ + UInt64 audioDataBytes = 0; + + if (m_audioDataByteCount > 0) { + audioDataBytes = m_audioDataByteCount; + } else { + audioDataBytes = contentLength() - m_metaDataSizeInBytes; + } + + return audioDataBytes; +} + +float Audio_Stream::durationInSeconds() +{ + if (m_audioDataPacketCount > 0 && m_srcFormat.mFramesPerPacket > 0) { + return m_audioDataPacketCount * m_srcFormat.mFramesPerPacket / m_srcFormat.mSampleRate; + } + + // Not enough data provided by the format, use bit rate based estimation + UInt64 audioDataBytes = audioDataByteCount(); + + if (audioDataBytes > 0) { + float bitrate = this->bitrate(); + + if (bitrate > 0) { + return audioDataBytes / (bitrate * 0.125); + } + } + + // No file length available, cannot calculate the duration + return 0; +} + +void Audio_Stream::seekToOffset(float offset) +{ + const State currentState = this->state(); + + if (!(currentState == PLAYING || currentState == END_OF_FILE)) { + // Do not allow seeking if we are not currently playing the stream + // This allows a previous seek to be completed + return; + } + + setState(SEEKING); + + m_originalContentLength = contentLength(); + + setDecoderRunState(false); + + pthread_mutex_lock(&m_packetQueueMutex); + m_numPacketsToRewind = 0; + pthread_mutex_unlock(&m_packetQueueMutex); + + m_inputStream->setScheduledInRunLoop(false); + + setSeekOffset(offset); + + if (m_seekTimer) { + CFRunLoopTimerInvalidate(m_seekTimer); + CFRelease(m_seekTimer); + m_seekTimer = 0; + } + + CFRunLoopTimerContext ctx = {0, this, NULL, NULL, NULL}; + + m_seekTimer = CFRunLoopTimerCreate(NULL, + CFAbsoluteTimeGetCurrent(), + 0.050, // 50 ms + 0, + 0, + seekTimerCallback, + &ctx); + + CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_seekTimer, kCFRunLoopCommonModes); +} + +Input_Stream_Position Audio_Stream::streamPositionForOffset(float offset) +{ + Input_Stream_Position position; + position.start = 0; + position.end = 0; + + const float duration = durationInSeconds(); + if (!(duration > 0)) { + return position; + } + + UInt64 seekByteOffset = m_dataOffset + offset * (contentLength() - m_dataOffset); + + position.start = seekByteOffset; + position.end = contentLength(); + + return position; +} + +float Audio_Stream::currentVolume() +{ + return m_outputVolume; +} + +void Audio_Stream::setDecoderRunState(bool decoderShouldRun) +{ + pthread_mutex_lock(&m_streamStateMutex); + + if (decoderShouldRun && m_decoderThreadCreated) { + if (m_decodeTimer == NULL) { + CFRunLoopTimerContext ctx = {0, this, NULL, NULL, NULL}; + + m_decodeTimer = CFRunLoopTimerCreate (NULL, CFAbsoluteTimeGetCurrent() + 0.02, 0.02, 0, 0, decodeSinglePacket, &ctx); + + pthread_mutex_unlock(&m_streamStateMutex); + + while (m_decodeRunLoop == NULL || !CFRunLoopIsWaiting(m_decodeRunLoop)) { + usleep(0); + } + + pthread_mutex_lock(&m_streamStateMutex); + + CFRunLoopAddTimer(m_decodeRunLoop, m_decodeTimer, kCFRunLoopCommonModes); + + AS_TRACE("decoder timer added!!!\n"); + } + } else { + if (m_decodeTimer != NULL) { + CFRunLoopRemoveTimer(m_decodeRunLoop, m_decodeTimer, kCFRunLoopCommonModes); + + CFRelease(m_decodeTimer); + m_decodeTimer = 0; + + AS_TRACE("decoder timer removed!!!\n"); + } + } + m_decoderShouldRun = decoderShouldRun; + pthread_mutex_unlock(&m_streamStateMutex); +} + +void Audio_Stream::setVolume(float volume) +{ + if (volume < 0) { + volume = 0; + } + if (volume > 1.0) { + volume = 1.0; + } + // Store the volume so it will be used consequently when the queue plays + m_outputVolume = volume; + + if (m_audioQueue) { + m_audioQueue->setVolume(volume); + } +} + +void Audio_Stream::setPlayRate(float playRate) +{ + if (m_audioQueue) { + m_audioQueue->setPlayRate(playRate); + } +} + +void Audio_Stream::setUrl(CFURLRef url) +{ + if (m_inputStream) { + delete m_inputStream; + m_inputStream = 0; + } + + if (HTTP_Stream::canHandleUrl(url)) { + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (config->cacheEnabled) { + Caching_Stream *cache = new Caching_Stream(new HTTP_Stream()); + + CFStringRef cacheIdentifier = createCacheIdentifierForURL(url); + + cache->setCacheIdentifier(cacheIdentifier); + + CFRelease(cacheIdentifier); + + m_inputStream = cache; + } else { + m_inputStream = new HTTP_Stream(); + } + + m_inputStream->m_delegate = this; + } else if (File_Stream::canHandleUrl(url)) { + m_inputStream = new File_Stream(); + m_inputStream->m_delegate = this; + } + + if (m_inputStream) { + m_inputStream->setUrl(url); + } +} + +void Audio_Stream::setStrictContentTypeChecking(bool strictChecking) +{ + m_strictContentTypeChecking = strictChecking; +} + +void Audio_Stream::setDefaultContentType(CFStringRef defaultContentType) +{ + if (m_defaultContentType) { + CFRelease(m_defaultContentType); + m_defaultContentType = 0; + } + if (defaultContentType) { + m_defaultContentType = CFStringCreateCopy(kCFAllocatorDefault, defaultContentType); + } +} + +void Audio_Stream::setSeekOffset(float offset) +{ + m_seekOffset = offset; +} + +void Audio_Stream::setDefaultContentLength(UInt64 defaultContentLength) +{ + m_defaultContentLength = defaultContentLength; +} + +void Audio_Stream::setContentLength(UInt64 contentLength) +{ + pthread_mutex_lock(&m_streamStateMutex); + m_contentLength = contentLength; + pthread_mutex_unlock(&m_streamStateMutex); +} + +void Audio_Stream::setPreloading(bool preloading) +{ + pthread_mutex_lock(&m_streamStateMutex); + m_preloading = preloading; + pthread_mutex_unlock(&m_streamStateMutex); +} + +bool Audio_Stream::isPreloading() +{ + pthread_mutex_lock(&m_streamStateMutex); + bool preloading = m_preloading; + pthread_mutex_unlock(&m_streamStateMutex); + return preloading; +} + +void Audio_Stream::setOutputFile(CFURLRef url) +{ + if (m_fileOutput) { + delete m_fileOutput; + m_fileOutput = 0; + } + if (url) { + m_fileOutput = new File_Output(url); + } + m_outputFile = url; +} + +CFURLRef Audio_Stream::outputFile() +{ + return m_outputFile; +} + +Audio_Stream::State Audio_Stream::state() +{ + pthread_mutex_lock(&m_streamStateMutex); + const State currentState = m_state; + pthread_mutex_unlock(&m_streamStateMutex); + + return currentState; +} + +CFStringRef Audio_Stream::sourceFormatDescription() +{ + unsigned char formatID[5]; + *(UInt32 *)formatID = OSSwapHostToBigInt32(m_srcFormat.mFormatID); + + formatID[4] = '\0'; + + CFStringRef formatDescription = CFStringCreateWithFormat(NULL, + NULL, + CFSTR("formatID: %s, sample rate: %f"), + formatID, + m_srcFormat.mSampleRate); + + return formatDescription; +} + +CFStringRef Audio_Stream::contentType() +{ + return m_contentType; +} + +CFStringRef Audio_Stream::createCacheIdentifierForURL(CFURLRef url) +{ + CFStringRef urlString = CFURLGetString(url); + CFStringRef urlHash = createHashForString(urlString); + + CFStringRef cacheIdentifier = CFStringCreateWithFormat(NULL, NULL, CFSTR("FSCache-%@"), urlHash); + + CFRelease(urlHash); + + return cacheIdentifier; +} + +size_t Audio_Stream::cachedDataSize() +{ + size_t dataSize = 0; + pthread_mutex_lock(&m_packetQueueMutex); + dataSize = m_cachedDataSize; + pthread_mutex_unlock(&m_packetQueueMutex); + return dataSize; +} + +bool Audio_Stream::strictContentTypeChecking() +{ + return m_strictContentTypeChecking; +} + +AudioFileTypeID Audio_Stream::audioStreamTypeFromContentType(CFStringRef contentType) +{ + AudioFileTypeID fileTypeHint = kAudioFileMP3Type; + + if (!contentType) { + AS_TRACE("***** Unable to detect the audio stream type: missing content-type! *****\n"); + goto out; + } + + if (CFStringCompare(contentType, CFSTR("audio/mpeg"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileMP3Type; + AS_TRACE("kAudioFileMP3Type detected\n"); + } else if (CFStringCompare(contentType, CFSTR("audio/x-wav"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileWAVEType; + AS_TRACE("kAudioFileWAVEType detected\n"); + } else if (CFStringCompare(contentType, CFSTR("audio/x-aifc"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileAIFCType; + AS_TRACE("kAudioFileAIFCType detected\n"); + } else if (CFStringCompare(contentType, CFSTR("audio/x-aiff"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileAIFFType; + AS_TRACE("kAudioFileAIFFType detected\n"); + } else if (CFStringCompare(contentType, CFSTR("audio/x-m4a"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileM4AType; + AS_TRACE("kAudioFileM4AType detected\n"); + } else if (CFStringCompare(contentType, CFSTR("audio/mp4"), 0) == kCFCompareEqualTo || + CFStringCompare(contentType, CFSTR("video/mp4"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileMPEG4Type; + AS_TRACE("kAudioFileMPEG4Type detected\n"); + } else if (CFStringCompare(contentType, CFSTR("audio/x-caf"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileCAFType; + AS_TRACE("kAudioFileCAFType detected\n"); + } else if (CFStringCompare(contentType, CFSTR("audio/aac"), 0) == kCFCompareEqualTo || + CFStringCompare(contentType, CFSTR("audio/aacp"), 0) == kCFCompareEqualTo) { + fileTypeHint = kAudioFileAAC_ADTSType; + AS_TRACE("kAudioFileAAC_ADTSType detected\n"); + } else { + AS_TRACE("***** Unable to detect the audio stream type *****\n"); + } + +out: + return fileTypeHint; +} + +void Audio_Stream::audioQueueStateChanged(Audio_Queue::State aqState) +{ + if (aqState == Audio_Queue::RUNNING && SEEKING != state()) { + invalidateWatchdogTimer(); + setState(PLAYING); + + float currentVolume = m_audioQueue->volume(); + + if (currentVolume != m_outputVolume) { + m_audioQueue->setVolume(m_outputVolume); + } + } else if (aqState == Audio_Queue::IDLE) { + setState(STOPPED); + } else if (aqState == Audio_Queue::PAUSED) { + setState(PAUSED); + } +} + +void Audio_Stream::audioQueueBuffersEmpty() +{ + AS_TRACE("%s: enter\n", __PRETTY_FUNCTION__); + + /* + * Entering here means that the audio queue has run out of data to play. + */ + + const int count = playbackDataCount(); + + /* + * If we don't have any cached data to play and we are still supposed to + * feed the audio queue with data, enter the buffering state. + */ + if (count == 0 && m_inputStreamRunning && FAILED != state()) { + Stream_Configuration *config = Stream_Configuration::configuration(); + + pthread_mutex_lock(&m_packetQueueMutex); + m_playPacket = m_queuedHead; + if (m_processedPackets.size() > 0) { + /* + * We have audio packets in memory (only case with a non-continuous stream), + * so figure out the correct location to set the playback pointer so that we don't + * start decoding the packets from the beginning when + * buffering resumes. + */ + queued_packet_t *firstPacket = m_processedPackets.front(); + queued_packet_t *cur = m_queuedHead; + while (cur) { + if (cur->identifier == firstPacket->identifier) { + break; + } + cur = cur->next; + } + if (cur) { + m_playPacket = cur; + } + } + pthread_mutex_unlock(&m_packetQueueMutex); + + // Always make sure we are scheduled to receive data if we start buffering + m_inputStream->setScheduledInRunLoop(true); + + AS_WARN("Audio queue run out data, starting buffering\n"); + + setState(BUFFERING); + + if (m_firstBufferingTime == 0) { + // Never buffered, just increase the counter + m_firstBufferingTime = CFAbsoluteTimeGetCurrent(); + m_bounceCount++; + + AS_TRACE("stream buffered, increasing bounce count %zu, interval %i\n", m_bounceCount, config->bounceInterval); + } else { + // Buffered before, calculate the difference + CFAbsoluteTime cur = CFAbsoluteTimeGetCurrent(); + + int diff = cur - m_firstBufferingTime; + + if (diff >= config->bounceInterval) { + // More than bounceInterval seconds passed from the last + // buffering. So not a continuous bouncing. Reset the + // counters. + m_bounceCount = 0; + m_firstBufferingTime = 0; + + AS_TRACE("%i seconds passed from last buffering, resetting counters, interval %i\n", diff, config->bounceInterval); + } else { + m_bounceCount++; + + AS_TRACE("%i seconds passed from last buffering, increasing bounce count to %zu, interval %i\n", diff, m_bounceCount, config->bounceInterval); + } + } + + // Check if we have reached the bounce state + if (m_bounceCount >= config->maxBounceCount) { + CFStringRef errorDescription = CFStringCreateWithFormat(NULL, NULL, CFSTR("Buffered %zu times in the last %i seconds"), m_bounceCount, config->maxBounceCount); + + closeAndSignalError(AS_ERR_BOUNCING, errorDescription); + if (errorDescription) { + CFRelease(errorDescription); + } + } + + // Create the watchdog in case the input stream gets stuck + createWatchdogTimer(); + + return; + } + + AS_TRACE("%i cached packets, enqueuing\n", count); + + // Keep enqueuing the packets in the queue until we have them + + pthread_mutex_lock(&m_packetQueueMutex); + if (m_playPacket && count > 0) { + pthread_mutex_unlock(&m_packetQueueMutex); + determineBufferingLimits(); + } else { + pthread_mutex_unlock(&m_packetQueueMutex); + + AS_TRACE("%s: closing the audio queue\n", __PRETTY_FUNCTION__); + + setState(PLAYBACK_COMPLETED); + + close(true); + } +} + +void Audio_Stream::audioQueueInitializationFailed() +{ + if (m_inputStreamRunning) { + if (m_inputStream) { + m_inputStream->close(); + } + m_inputStreamRunning = false; + } + + setState(FAILED); + + if (m_delegate) { + if (audioQueue()->m_lastError == kAudioFormatUnsupportedDataFormatError) { + m_delegate->audioStreamErrorOccurred(AS_ERR_UNSUPPORTED_FORMAT, CFSTR("Audio queue failed, unsupported format")); + } else { + CFStringRef errorDescription = coreAudioErrorToCFString(CFSTR("Audio queue failed"), audioQueue()->m_lastError); + m_delegate->audioStreamErrorOccurred(AS_ERR_STREAM_PARSE, errorDescription); + if (errorDescription) { + CFRelease(errorDescription); + } + } + } +} + +void Audio_Stream::audioQueueFinishedPlayingPacket() +{ +} + +void Audio_Stream::streamIsReadyRead() +{ + if (m_audioStreamParserRunning) { + AS_TRACE("%s: parser already running!\n", __PRETTY_FUNCTION__); + return; + } + + CFStringRef audioContentType = CFSTR("audio/"); + CFStringRef videoContentType = CFSTR("video/"); + const CFIndex audioContentTypeLen = CFStringGetLength(audioContentType); + const CFIndex videoContentTypeLen = CFStringGetLength(videoContentType); + bool matchesAudioContentType = false; + + CFStringRef contentType = 0; + + if (m_inputStream) { + contentType = m_inputStream->contentType(); + } + + if (m_contentType) { + CFRelease(m_contentType); + m_contentType = 0; + } + if (contentType) { + m_contentType = CFStringCreateCopy(kCFAllocatorDefault, contentType); + const CFIndex contentTypeLen = CFStringGetLength(contentType); + + if (contentTypeLen >= audioContentTypeLen && + (kCFCompareEqualTo == CFStringCompareWithOptions(contentType, audioContentType, CFRangeMake(0, audioContentTypeLen),0))) { + matchesAudioContentType = true; + } else if (contentTypeLen >= videoContentTypeLen && + (kCFCompareEqualTo == CFStringCompareWithOptions(contentType, videoContentType, CFRangeMake(0, videoContentTypeLen),0))) { + matchesAudioContentType = true; + } + } + + if (m_strictContentTypeChecking && !matchesAudioContentType) { + CFStringRef errorDescription = NULL; + + if (m_contentType) { + errorDescription = CFStringCreateWithFormat(NULL, NULL, CFSTR("Strict content type checking active, %@ is not an audio content type"), m_contentType); + } else { + errorDescription = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Strict content type checking active, no content type provided by the server")); + } + + closeAndSignalError(AS_ERR_OPEN, errorDescription); + if (errorDescription) { + CFRelease(errorDescription); + } + return; + } + + m_audioDataByteCount = 0; + + /* OK, it should be an audio stream, let's try to open it */ + OSStatus result = AudioFileStreamOpen(this, + propertyValueCallback, + streamDataCallback, + audioStreamTypeFromContentType((contentType ? contentType : m_defaultContentType)), + &m_audioFileStream); + + if (result == 0) { + AS_TRACE("%s: audio file stream opened.\n", __PRETTY_FUNCTION__); + m_audioStreamParserRunning = true; + } else { + closeAndSignalError(AS_ERR_OPEN, CFSTR("Audio file stream parser open error")); + } +} + +void Audio_Stream::streamHasBytesAvailable(UInt8 *data, UInt32 numBytes) +{ + AS_TRACE("%s: %u bytes\n", __FUNCTION__, (unsigned int)numBytes); + + if (!m_inputStreamRunning) { + AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__); + return; + } + + pthread_mutex_lock(&m_packetQueueMutex); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (m_cachedDataSize >= config->maxPrebufferedByteCount) { + pthread_mutex_unlock(&m_packetQueueMutex); + + // If we got a cache overflow, disable the input stream so that we don't get more data + m_inputStream->setScheduledInRunLoop(false); + + // Schedule a timer to watch when we can enable the input stream again + if (m_inputStreamTimer) { + CFRunLoopTimerInvalidate(m_inputStreamTimer); + CFRelease(m_inputStreamTimer); + m_inputStreamTimer = 0; + } + + CFRunLoopTimerContext ctx = {0, this, NULL, NULL, NULL}; + + m_inputStreamTimer = CFRunLoopTimerCreate(NULL, + CFAbsoluteTimeGetCurrent(), + 0.1, // 100 ms + 0, + 0, + inputStreamTimerCallback, + &ctx); + + CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_inputStreamTimer, kCFRunLoopCommonModes); + } else { + pthread_mutex_unlock(&m_packetQueueMutex); + } + + bool decoderFailed = false; + + pthread_mutex_lock(&m_streamStateMutex); + decoderFailed = m_decoderFailed; + pthread_mutex_unlock(&m_streamStateMutex); + + if (decoderFailed) { + closeAndSignalError(AS_ERR_TERMINATED, CFSTR("Stream terminated abrubtly")); + return; + } + + m_bytesReceived += numBytes; + + if (m_fileOutput) { + m_fileOutput->write(data, numBytes); + } + + if (m_audioStreamParserRunning) { + OSStatus result = AudioFileStreamParseBytes(m_audioFileStream, numBytes, data, (m_discontinuity ? kAudioFileStreamParseFlag_Discontinuity : 0)); + + if (result != 0) { + AS_TRACE("%s: AudioFileStreamParseBytes error %d\n", __PRETTY_FUNCTION__, (int)result); + + if (result == kAudioFileStreamError_NotOptimized) { + closeAndSignalError(AS_ERR_UNSUPPORTED_FORMAT, CFSTR("Non-optimized formats not supported for streaming")); + } else { + CFStringRef errorDescription = coreAudioErrorToCFString(CFSTR("Audio file stream parse bytes error"), result); + closeAndSignalError(AS_ERR_STREAM_PARSE, errorDescription); + if (errorDescription) { + CFRelease(errorDescription); + } + } + } else if (m_initializationError == kAudioConverterErr_FormatNotSupported) { + CFStringRef sourceFormat = sourceFormatDescription(); + + CFStringRef errorDescription = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ not supported for streaming"), sourceFormat); + + closeAndSignalError(AS_ERR_UNSUPPORTED_FORMAT, errorDescription); + if (errorDescription) { + CFRelease(errorDescription); + } + if (sourceFormat) { + CFRelease(sourceFormat); + } + } else if (m_initializationError != noErr) { + CFStringRef errorDescription = coreAudioErrorToCFString(CFSTR("Error in audio stream initialization"), m_initializationError); + closeAndSignalError(AS_ERR_OPEN, errorDescription); + if (errorDescription) { + CFRelease(errorDescription); + } + } else { + m_discontinuity = false; + } + } +} + +void Audio_Stream::streamEndEncountered() +{ + AS_TRACE("%s\n", __PRETTY_FUNCTION__); + + if (!m_inputStreamRunning) { + AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__); + return; + } + + if (!(contentLength() > 0)) { + /* Continuous streams are not supposed to end */ + + closeAndSignalError(AS_ERR_NETWORK, CFSTR("Stream ended abruptly")); + + return; + } + + setState(END_OF_FILE); + + if (m_inputStream) { + m_inputStream->close(); + } + m_inputStreamRunning = false; +} + +void Audio_Stream::streamErrorOccurred(CFStringRef errorDesc) +{ + AS_TRACE("%s\n", __PRETTY_FUNCTION__); + + if (!m_inputStreamRunning) { + AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__); + return; + } + + closeAndSignalError(AS_ERR_NETWORK, errorDesc); +} + +void Audio_Stream::streamMetaDataAvailable(std::map metaData) +{ + if (m_delegate) { + m_delegate->audioStreamMetaDataAvailable(metaData); + } +} + +void Audio_Stream::streamMetaDataByteSizeAvailable(UInt32 sizeInBytes) +{ + m_metaDataSizeInBytes = sizeInBytes; + + AS_TRACE("metadata size received %i\n", m_metaDataSizeInBytes); +} + +/* private */ + +CFStringRef Audio_Stream::createHashForString(CFStringRef str) +{ + UInt8 buf[4096]; + CFIndex usedBytes = 0; + + CFStringGetBytes(str, + CFRangeMake(0, CFStringGetLength(str)), + kCFStringEncodingUTF8, + '?', + false, + buf, + 4096, + &usedBytes); + + CC_SHA1_CTX hashObject; + CC_SHA1_Init(&hashObject); + + CC_SHA1_Update(&hashObject, + (const void *)buf, + (CC_LONG)usedBytes); + + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1_Final(digest, &hashObject); + + char hash[2 * sizeof(digest) + 1]; + for (size_t i = 0; i < sizeof(digest); ++i) { + snprintf(hash + (2 * i), 3, "%02x", (int)(digest[i])); + } + + return CFStringCreateWithCString(kCFAllocatorDefault, + (const char *)hash, + kCFStringEncodingUTF8); +} + +Audio_Queue* Audio_Stream::audioQueue() +{ + if (!m_audioQueue) { + AS_TRACE("No audio queue, creating\n"); + + m_audioQueue = new Audio_Queue(); + + m_audioQueue->m_delegate = this; + m_audioQueue->m_streamDesc = m_dstFormat; + + m_audioQueue->m_initialOutputVolume = m_outputVolume; + } + return m_audioQueue; +} + +void Audio_Stream::closeAudioQueue() +{ + if (!m_audioQueue) { + return; + } + + AS_TRACE("Releasing audio queue\n"); + + pthread_mutex_lock(&m_streamStateMutex); + m_audioQueueConsumedPackets = false; + pthread_mutex_unlock(&m_streamStateMutex); + + m_audioQueue->m_delegate = 0; + delete m_audioQueue; + m_audioQueue = 0; +} + +UInt64 Audio_Stream::defaultContentLength() +{ + return m_defaultContentLength; +} + +UInt64 Audio_Stream::contentLength() +{ + pthread_mutex_lock(&m_streamStateMutex); + if (m_contentLength == 0) { + if (m_inputStream) { + m_contentLength = m_inputStream->contentLength(); + if (m_contentLength == 0) { + m_contentLength = defaultContentLength(); + } + } + } + pthread_mutex_unlock(&m_streamStateMutex); + return m_contentLength; +} + +void Audio_Stream::closeAndSignalError(int errorCode, CFStringRef errorDescription) +{ + AS_TRACE("%s: error %i\n", __PRETTY_FUNCTION__, errorCode); + + setState(FAILED); + close(true); + + if (m_delegate) { + m_delegate->audioStreamErrorOccurred(errorCode, errorDescription); + } +} + +void Audio_Stream::setState(State state) +{ + pthread_mutex_lock(&m_streamStateMutex); + + if (m_state == state) { + pthread_mutex_unlock(&m_streamStateMutex); + + return; + } + +#if defined (AS_DEBUG) + + switch (state) { + case BUFFERING: + AS_TRACE("state set: BUFFERING\n"); + break; + case PLAYING: + AS_TRACE("state set: PLAYING\n"); + break; + case PAUSED: + AS_TRACE("state set: PAUSED\n"); + break; + case SEEKING: + AS_TRACE("state set: SEEKING\n"); + break; + case FAILED: + AS_TRACE("state set: FAILED\n"); + break; + case END_OF_FILE: + AS_TRACE("state set: END_OF_FILE\n"); + break; + case PLAYBACK_COMPLETED: + AS_TRACE("state set: PLAYBACK_COMPLETED\n"); + break; + default: + AS_TRACE("unknown state\n"); + break; + } +#endif + + m_state = state; + + pthread_mutex_unlock(&m_streamStateMutex); + + if (m_delegate) { + m_delegate->audioStreamStateChanged(state); + } +} + +void Audio_Stream::setCookiesForStream(AudioFileStreamID inAudioFileStream) +{ + OSStatus err; + + // get the cookie size + UInt32 cookieSize; + Boolean writable; + + err = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable); + if (err) { + return; + } + + // get the cookie data + void* cookieData = calloc(1, cookieSize); + err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData); + if (err) { + free(cookieData); + return; + } + + // set the cookie on the queue. + if (m_audioConverter) { + AudioConverterSetProperty(m_audioConverter, kAudioConverterDecompressionMagicCookie, cookieSize, cookieData); + } + + free(cookieData); +} + +float Audio_Stream::bitrate() +{ + // Use the stream provided bit rate, if available + if (m_bitRate > 0) { + return m_bitRate; + } + + // Stream didn't provide a bit rate, so let's calculate it + if (m_bitrateBufferIndex < kAudioStreamBitrateBufferSize) { + return 0; + } + double sum = 0; + + for (size_t i=0; i < kAudioStreamBitrateBufferSize; i++) { + sum += m_bitrateBuffer[i]; + } + + return sum / (float)kAudioStreamBitrateBufferSize; +} + +void Audio_Stream::watchdogTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + Audio_Stream *THIS = (Audio_Stream *)info; + + pthread_mutex_lock(&THIS->m_streamStateMutex); + + if (!THIS->m_audioQueueConsumedPackets) { + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + CFStringRef errorDescription = CFStringCreateWithFormat(NULL, NULL, CFSTR("The stream startup watchdog activated: stream didn't start to play in %d seconds"), config->startupWatchdogPeriod); + + THIS->closeAndSignalError(AS_ERR_OPEN, errorDescription); + if (errorDescription) { + CFRelease(errorDescription); + } + } else { + pthread_mutex_unlock(&THIS->m_streamStateMutex); + } +} + +void Audio_Stream::seekTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + Audio_Stream *THIS = (Audio_Stream *)info; + + if (THIS->state() != SEEKING) { + return; + } + + pthread_mutex_lock(&THIS->m_streamStateMutex); + + AS_TRACE("decoder free, seeking\n"); + + if (THIS->m_seekTimer) { + CFRunLoopTimerInvalidate(THIS->m_seekTimer); + CFRelease(THIS->m_seekTimer); + THIS->m_seekTimer = 0; + } + + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + // Close the audio queue so that it won't ask any more data + THIS->closeAudioQueue(); + + Input_Stream_Position position = THIS->streamPositionForOffset(THIS->m_seekOffset); + + if (position.start == 0 && position.end == 0) { + THIS->closeAndSignalError(AS_ERR_NETWORK, CFSTR("Failed to retrieve seeking position")); + return; + } + + const float duration = THIS->durationInSeconds(); + const double packetDuration = THIS->m_srcFormat.mFramesPerPacket / THIS->m_srcFormat.mSampleRate; + + if (packetDuration > 0) { + UInt32 ioFlags = 0; + SInt64 packetAlignedByteOffset; + SInt64 seekPacket = floor((duration * THIS->m_seekOffset) / packetDuration); + + THIS->m_playingPacketIdentifier = seekPacket; + + OSStatus err = AudioFileStreamSeek(THIS->m_audioFileStream, seekPacket, &packetAlignedByteOffset, &ioFlags); + if (!err) { + position.start = packetAlignedByteOffset + THIS->m_dataOffset; + } else { + THIS->closeAndSignalError(AS_ERR_NETWORK, CFSTR("Failed to calculate seeking position")); + return; + } + } else { + THIS->closeAndSignalError(AS_ERR_NETWORK, CFSTR("Failed to calculate seeking position")); + return; + } + + Stream_Configuration *config = Stream_Configuration::configuration(); + + // Do a cache lookup if we can find the seeked packet from the cache and no need to + // open the stream from the new position + bool foundCachedPacket = false; + queued_packet_t *seekPacket = 0; + + if (config->seekingFromCacheEnabled) { + AS_LOCK_TRACE("lock: seekToOffset\n"); + pthread_mutex_lock(&THIS->m_packetQueueMutex); + + queued_packet_t *cur = THIS->m_queuedHead; + while (cur) { + if (cur->identifier == THIS->m_playingPacketIdentifier) { + foundCachedPacket = true; + seekPacket = cur; + break; + } + + queued_packet_t *tmp = cur->next; + cur = tmp; + } + + AS_LOCK_TRACE("unlock: seekToOffset\n"); + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + } else { + AS_TRACE("Seeking from cache disabled\n"); + } + + if (!foundCachedPacket) { + AS_TRACE("Seeked packet not found from cache, reopening the input stream\n"); + + // Close but keep the stream parser running + THIS->close(false); + + THIS->m_bytesReceived = 0; + THIS->m_bounceCount = 0; + THIS->m_firstBufferingTime = 0; + THIS->m_bitrateBufferIndex = 0; + THIS->m_initializationError = noErr; + THIS->m_converterRunOutOfData = false; + THIS->m_discontinuity = true; + + bool success = THIS->m_inputStream->open(position); + + if (success) { + THIS->setContentLength(THIS->m_originalContentLength); + + pthread_mutex_lock(&THIS->m_streamStateMutex); + + if (THIS->m_audioConverter) { + AudioConverterDispose(THIS->m_audioConverter); + } + OSStatus err = AudioConverterNew(&(THIS->m_srcFormat), + &(THIS->m_dstFormat), + &(THIS->m_audioConverter)); + if (err) { + THIS->closeAndSignalError(AS_ERR_OPEN, CFSTR("Error in creating an audio converter")); + pthread_mutex_unlock(&THIS->m_streamStateMutex); + return; + } + + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + THIS->setState(BUFFERING); + + THIS->m_inputStreamRunning = true; + + } else { + THIS->closeAndSignalError(AS_ERR_OPEN, CFSTR("Input stream open error")); + return; + } + } else { + AS_TRACE("Seeked packet found from cache!\n"); + + // Found the packet from the cache, let's use the cache directly. + + pthread_mutex_lock(&THIS->m_packetQueueMutex); + THIS->m_playPacket = seekPacket; + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + THIS->m_discontinuity = true; + + THIS->setState(PLAYING); + } + + THIS->audioQueue()->init(); + + THIS->m_inputStream->setScheduledInRunLoop(true); + + THIS->setDecoderRunState(true); +} + +void Audio_Stream::inputStreamTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + Audio_Stream *THIS = (Audio_Stream *)info; + + if (!THIS->m_inputStreamRunning) { + if (THIS->m_inputStreamTimer) { + CFRunLoopTimerInvalidate(THIS->m_inputStreamTimer); + CFRelease(THIS->m_inputStreamTimer); + THIS->m_inputStreamTimer = 0; + } + + return; + } + + pthread_mutex_lock(&THIS->m_packetQueueMutex); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (THIS->m_cachedDataSize < config->maxPrebufferedByteCount) { + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + + THIS->m_inputStream->setScheduledInRunLoop(true); + } else { + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + } +} + +void Audio_Stream::stateSetTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + Audio_Stream *THIS = (Audio_Stream *)info; + + pthread_mutex_lock(&THIS->m_streamStateMutex); + + if (THIS->m_stateSetTimer) { + // Timer is automatically invalidated as it fires only once + CFRelease(THIS->m_stateSetTimer); + THIS->m_stateSetTimer = 0; + } + + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + THIS->setState(PLAYING); +} + +bool Audio_Stream::decoderShouldRun() +{ + const Audio_Stream::State state = this->state(); + + pthread_mutex_lock(&m_streamStateMutex); + + if (m_preloading || + !m_decoderShouldRun || + m_converterRunOutOfData || + m_decoderFailed || + state == PAUSED || + state == STOPPED || + state == SEEKING || + state == FAILED || + state == PLAYBACK_COMPLETED || + m_dstFormat.mBytesPerPacket == 0) { + pthread_mutex_unlock(&m_streamStateMutex); + return false; + } else { + pthread_mutex_unlock(&m_streamStateMutex); + return true; + } +} + +void Audio_Stream::decodeSinglePacket(CFRunLoopTimerRef timer, void *info) +{ + Audio_Stream *THIS = (Audio_Stream *)info; + + pthread_mutex_lock(&THIS->m_streamStateMutex); + + if (THIS->m_decoderShouldRun && THIS->m_converterRunOutOfData) { + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + // Check if we got more data so we can run the decoder again + pthread_mutex_lock(&THIS->m_packetQueueMutex); + + if (THIS->m_playPacket) { + // Yes, got data again + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + + AS_TRACE("Converter run out of data: more data available. Restarting the audio converter\n"); + + pthread_mutex_lock(&THIS->m_streamStateMutex); + + if (THIS->m_audioConverter) { + AudioConverterDispose(THIS->m_audioConverter); + } + OSStatus err = AudioConverterNew(&(THIS->m_srcFormat), + &(THIS->m_dstFormat), + &(THIS->m_audioConverter)); + if (err) { + AS_TRACE("Error in creating an audio converter, error %i\n", err); + THIS->m_decoderFailed = true; + } + THIS->m_converterRunOutOfData = false; + + pthread_mutex_unlock(&THIS->m_streamStateMutex); + } else { + AS_TRACE("decoder: converter run out data: bailing out\n"); + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + } + } else { + pthread_mutex_unlock(&THIS->m_streamStateMutex); + } + + if (!THIS->decoderShouldRun()) { + return; + } + + AudioBufferList outputBufferList; + outputBufferList.mNumberBuffers = 1; + outputBufferList.mBuffers[0].mNumberChannels = THIS->m_dstFormat.mChannelsPerFrame; + outputBufferList.mBuffers[0].mDataByteSize = THIS->m_outputBufferSize; + outputBufferList.mBuffers[0].mData = THIS->m_outputBuffer; + + AudioStreamPacketDescription description; + description.mStartOffset = 0; + description.mDataByteSize = THIS->m_outputBufferSize; + description.mVariableFramesInPacket = 0; + + UInt32 ioOutputDataPackets = THIS->m_outputBufferSize / THIS->m_dstFormat.mBytesPerPacket; + + AS_TRACE("calling AudioConverterFillComplexBuffer\n"); + + pthread_mutex_lock(&THIS->m_packetQueueMutex); + + if (THIS->m_numPacketsToRewind > 0) { + AS_TRACE("Rewinding %i packets\n", THIS->m_numPacketsToRewind); + + queued_packet_t *front = THIS->m_playPacket; + + while (front && THIS->m_numPacketsToRewind-- > 0) { + queued_packet_t *tmp = front->next; + + front = tmp; + } + + THIS->m_playPacket = front; + THIS->m_numPacketsToRewind = 0; + } + + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + + OSStatus err = AudioConverterFillComplexBuffer(THIS->m_audioConverter, + &encoderDataCallback, + THIS, + &ioOutputDataPackets, + &outputBufferList, + NULL); + + pthread_mutex_lock(&THIS->m_streamStateMutex); + + if (err == noErr && THIS->m_decoderShouldRun) { + THIS->m_audioQueueConsumedPackets = true; + + if (THIS->m_state != PLAYING && !THIS->m_stateSetTimer) { + // Set the playing state in the main thread + + CFRunLoopTimerContext ctx = {0, THIS, NULL, NULL, NULL}; + + THIS->m_stateSetTimer = CFRunLoopTimerCreate(NULL, 0, 0, 0, 0, + stateSetTimerCallback, + &ctx); + + CFRunLoopAddTimer(THIS->m_mainRunLoop, THIS->m_stateSetTimer, kCFRunLoopCommonModes); + } + + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + // This blocks until the queue has been able to consume the packets + THIS->audioQueue()->handleAudioPackets(outputBufferList.mBuffers[0].mDataByteSize, + outputBufferList.mNumberBuffers, + outputBufferList.mBuffers[0].mData, + &description); + + const UInt32 nFrames = outputBufferList.mBuffers[0].mDataByteSize / THIS->m_dstFormat.mBytesPerFrame; + + if (THIS->m_delegate) { + THIS->m_delegate->samplesAvailable(&outputBufferList, nFrames, description); + } + + Stream_Configuration *config = Stream_Configuration::configuration(); + + const bool continuous = (!(THIS->contentLength() > 0)); + + pthread_mutex_lock(&THIS->m_packetQueueMutex); + + /* The only reason we keep the already converted packets in memory + * is seeking from the cache. If in-memory seeking is disabled we + * can just cleanup the cache immediately. The same applies for + * continuous streams. They are never seeked backwards. + */ + if (!config->seekingFromCacheEnabled || + continuous || + THIS->m_cachedDataSize >= config->maxPrebufferedByteCount) { + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + + THIS->cleanupCachedData(); + } else { + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + } + } else if (err == kAudio_ParamError) { + AS_TRACE("decoder: converter param error\n"); + /* + * This means that iOS terminated background audio. Stream must be restarted. + * Signal an error so that the app can handle it. + */ + THIS->m_decoderFailed = true; + + pthread_mutex_unlock(&THIS->m_streamStateMutex); + } else { + pthread_mutex_unlock(&THIS->m_streamStateMutex); + } +} + + +void *Audio_Stream::decodeLoop(void *data) +{ + Audio_Stream *THIS = (Audio_Stream *)data; + + pthread_mutex_lock(&THIS->m_streamStateMutex); + THIS->m_decodeRunLoop = CFRunLoopGetCurrent(); + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + /* + * Silly timer to make the runloop to wake up every 5ms + */ + CFRunLoopTimerContext ctx = {0, NULL, NULL, NULL, NULL}; + CFRunLoopTimerRef tickTimer = CFRunLoopTimerCreate (NULL, CFAbsoluteTimeGetCurrent() + 0.005, 0.005, 0, 0, fsTick, &ctx); + CFRunLoopAddTimer(THIS->m_decodeRunLoop, tickTimer, kCFRunLoopCommonModes); + + CFRunLoopRun(); + + CFRunLoopRemoveTimer(THIS->m_decodeRunLoop, tickTimer, kCFRunLoopCommonModes); + CFRelease(tickTimer); + + AS_TRACE("returning from decodeLoop, bye\n"); + + return 0; +} + +void Audio_Stream::createWatchdogTimer() +{ + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (!(config->startupWatchdogPeriod > 0)) { + return; + } + + invalidateWatchdogTimer(); + + /* + * Start the WD if we have one requested. In this way we can track + * that the stream doesn't stuck forever on the buffering state + * (for instance some network error condition) + */ + + CFRunLoopTimerContext ctx = {0, this, NULL, NULL, NULL}; + + m_watchdogTimer = CFRunLoopTimerCreate(NULL, + CFAbsoluteTimeGetCurrent() + config->startupWatchdogPeriod, + 0, + 0, + 0, + watchdogTimerCallback, + &ctx); + + AS_TRACE("Starting the startup watchdog, period %i seconds\n", config->startupWatchdogPeriod); + + CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_watchdogTimer, kCFRunLoopCommonModes); +} + +void Audio_Stream::invalidateWatchdogTimer() +{ + if (m_watchdogTimer) { + CFRunLoopTimerInvalidate(m_watchdogTimer); + CFRelease(m_watchdogTimer); + m_watchdogTimer = 0; + + AS_TRACE("Watchdog invalidated\n"); + } +} + +int Audio_Stream::cachedDataCount() +{ + AS_LOCK_TRACE("lock: cachedDataCount\n"); + pthread_mutex_lock(&m_packetQueueMutex); + + int count = 0; + queued_packet_t *cur = m_queuedHead; + while (cur) { + cur = cur->next; + count++; + } + + AS_LOCK_TRACE("unlock: cachedDataCount\n"); + pthread_mutex_unlock(&m_packetQueueMutex); + + return count; +} + +int Audio_Stream::playbackDataCount() +{ + AS_LOCK_TRACE("lock: playbackDataCount\n"); + pthread_mutex_lock(&m_packetQueueMutex); + + int count = 0; + queued_packet_t *cur = m_playPacket; + while (cur) { + cur = cur->next; + count++; + } + + AS_LOCK_TRACE("unlock: playbackDataCount\n"); + pthread_mutex_unlock(&m_packetQueueMutex); + + return count; +} + +AudioQueueLevelMeterState Audio_Stream::levels() +{ + return audioQueue()->levels(); +} + +void Audio_Stream::determineBufferingLimits() +{ + if (state() == PAUSED || state() == SEEKING) { + return; + } + + Stream_Configuration *config = Stream_Configuration::configuration(); + + const bool continuous = (!(contentLength() > 0)); + + if (!m_initialBufferingCompleted) { + // Check if we have enough prebuffered data to start playback + + AS_TRACE("initial buffering not completed, checking if enough data\n"); + + if (config->usePrebufferSizeCalculationInPackets) { + const int packetCount = cachedDataCount(); + + if (packetCount >= config->requiredInitialPrebufferedPacketCount) { + AS_TRACE("More than %i packets prebuffered, required %i packets. Playback can be started\n", + packetCount, + config->requiredInitialPrebufferedPacketCount); + + m_initialBufferingCompleted = true; + + setDecoderRunState(true); + + return; + } + } + + int lim; + + if (continuous) { + // Continuous stream + lim = config->requiredInitialPrebufferedByteCountForContinuousStream; + AS_TRACE("continuous stream, %i bytes must be cached to start the playback\n", lim); + } else { + // Non-continuous + lim = config->requiredInitialPrebufferedByteCountForNonContinuousStream; + AS_TRACE("non-continuous stream, %i bytes must be cached to start the playback\n", lim); + } + + pthread_mutex_lock(&m_packetQueueMutex); + if (m_cachedDataSize > lim) { + pthread_mutex_unlock(&m_packetQueueMutex); + AS_TRACE("buffered %zu bytes, required for playback %i, starting playback\n", m_cachedDataSize, lim); + + m_initialBufferingCompleted = true; + + setDecoderRunState(true); + } else { + pthread_mutex_unlock(&m_packetQueueMutex); + AS_TRACE("not enough cached data to start playback\n"); + } + } + + // If the stream has never started playing and we have received 90% of the data of the stream, + // let's override the limits + bool audioQueueConsumedPackets = false; + pthread_mutex_lock(&m_streamStateMutex); + audioQueueConsumedPackets = m_audioQueueConsumedPackets; + pthread_mutex_unlock(&m_streamStateMutex); + + if (!audioQueueConsumedPackets && contentLength() > 0) { + Stream_Configuration *config = Stream_Configuration::configuration(); + + const UInt64 seekLength = contentLength() * m_seekOffset; + + AS_TRACE("seek length %llu\n", seekLength); + + const UInt64 numBytesRequiredToBeBuffered = (contentLength() - seekLength) * 0.9; + + AS_TRACE("audio queue not consumed packets, content length %llu, required bytes to be buffered %llu\n", contentLength(), numBytesRequiredToBeBuffered); + + if (m_bytesReceived >= numBytesRequiredToBeBuffered || + m_bytesReceived >= config->maxPrebufferedByteCount * 0.9) { + m_initialBufferingCompleted = true; + setDecoderRunState(true); + + AS_TRACE("%llu bytes received, overriding buffering limits\n", m_bytesReceived); + } + } +} + +void Audio_Stream::cleanupCachedData() +{ + pthread_mutex_lock(&m_streamStateMutex); + + if (!m_decoderShouldRun) { + AS_TRACE("cleanupCachedData: decoder should not run, bailing out!\n"); + pthread_mutex_unlock(&m_streamStateMutex); + return; + } else { + pthread_mutex_unlock(&m_streamStateMutex); + } + + AS_LOCK_TRACE("cleanupCachedData: lock\n"); + pthread_mutex_lock(&m_packetQueueMutex); + + queued_packet_t *cur = m_queuedHead; + /* Incoming (not yet processed) packets are added at the end (tail) + of the queue. Hence the processed packets reside in the front + of the queue. Clean the packets until we found the last packet + which is still needed. + */ + for (;;) { + if (cur && m_playPacket && cur->identifier == m_playPacket->identifier) { + break; + } + if (cur && !m_processedPackets.empty() && + cur->identifier == m_processedPackets.back()->identifier) { + queued_packet_t *nextPacket = cur->next; + + m_cachedDataSize -= cur->desc.mDataByteSize; + + free(cur); + cur = nextPacket; + m_processedPackets.pop_back(); + } else { + break; + } + } + m_queuedHead = cur; + + AS_LOCK_TRACE("cleanupCachedData: unlock\n"); + pthread_mutex_unlock(&m_packetQueueMutex); +} + +OSStatus Audio_Stream::encoderDataCallback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) +{ + Audio_Stream *THIS = (Audio_Stream *)inUserData; + + AS_TRACE("encoderDataCallback called\n"); + + AS_LOCK_TRACE("encoderDataCallback 1: lock\n"); + pthread_mutex_lock(&THIS->m_packetQueueMutex); + + // Dequeue one packet per time for the decoder + queued_packet_t *front = THIS->m_playPacket; + + if (!front) { + /* Don't deadlock */ + AS_LOCK_TRACE("encoderDataCallback 2: unlock\n"); + + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + + /* + * End of stream - Inside your input procedure, you must set the total amount of packets read and the sizes of the data in the AudioBufferList to zero. The input procedure should also return noErr. This will signal the AudioConverter that you are out of data. More specifically, set ioNumberDataPackets and ioBufferList->mDataByteSize to zero in your input proc and return noErr. Where ioNumberDataPackets is the amount of data converted and ioBufferList->mDataByteSize is the size of the amount of data converted in each AudioBuffer within your input procedure callback. Your input procedure may be called a few more times; you should just keep returning zero and noErr. + */ + + pthread_mutex_lock(&THIS->m_streamStateMutex); + THIS->m_converterRunOutOfData = true; + pthread_mutex_unlock(&THIS->m_streamStateMutex); + + *ioNumberDataPackets = 0; + + ioData->mBuffers[0].mDataByteSize = 0; + + return noErr; + } + + *ioNumberDataPackets = 1; + + ioData->mBuffers[0].mData = front->data; + ioData->mBuffers[0].mDataByteSize = front->desc.mDataByteSize; + ioData->mBuffers[0].mNumberChannels = THIS->m_srcFormat.mChannelsPerFrame; + + if (outDataPacketDescription) { + *outDataPacketDescription = &front->desc; + } + + THIS->m_playPacket = front->next; + + THIS->m_processedPackets.push_front(front); + + AS_LOCK_TRACE("encoderDataCallback 5: unlock\n"); + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + + return noErr; +} + +/* This is called by audio file stream parser when it finds property values */ +void Audio_Stream::propertyValueCallback(void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags) +{ + AS_TRACE("%s\n", __PRETTY_FUNCTION__); + Audio_Stream *THIS = static_cast(inClientData); + + if (!THIS->m_audioStreamParserRunning) { + AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__); + return; + } + + switch (inPropertyID) { + case kAudioFileStreamProperty_BitRate: { + bool sizeReceivedForFirstTime = (THIS->m_bitRate == 0); + UInt32 bitRateSize = sizeof(THIS->m_bitRate); + OSStatus err = AudioFileStreamGetProperty(inAudioFileStream, + kAudioFileStreamProperty_BitRate, + &bitRateSize, &THIS->m_bitRate); + if (err) { + THIS->m_bitRate = 0; + } else { + if (THIS->m_delegate && sizeReceivedForFirstTime) { + THIS->m_delegate->bitrateAvailable(); + } + } + break; + } + case kAudioFileStreamProperty_DataOffset: { + SInt64 offset; + UInt32 offsetSize = sizeof(offset); + OSStatus result = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &offset); + if (result == 0) { + THIS->m_dataOffset = offset; + } else { + AS_TRACE("%s: reading kAudioFileStreamProperty_DataOffset property failed\n", __PRETTY_FUNCTION__); + } + + break; + } + case kAudioFileStreamProperty_AudioDataByteCount: { + UInt32 byteCountSize = sizeof(THIS->m_audioDataByteCount); + OSStatus err = AudioFileStreamGetProperty(inAudioFileStream, + kAudioFileStreamProperty_AudioDataByteCount, + &byteCountSize, &THIS->m_audioDataByteCount); + if (err) { + THIS->m_audioDataByteCount = 0; + } + break; + } + case kAudioFileStreamProperty_AudioDataPacketCount: { + UInt32 packetCountSize = sizeof(THIS->m_audioDataPacketCount); + OSStatus err = AudioFileStreamGetProperty(inAudioFileStream, + kAudioFileStreamProperty_AudioDataPacketCount, + &packetCountSize, &THIS->m_audioDataPacketCount); + if (err) { + THIS->m_audioDataPacketCount = 0; + } + break; + } + case kAudioFileStreamProperty_ReadyToProducePackets: { + memset(&(THIS->m_srcFormat), 0, sizeof THIS->m_srcFormat); + UInt32 asbdSize = sizeof(THIS->m_srcFormat); + UInt32 formatListSize = 0; + Boolean writable; + OSStatus err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &(THIS->m_srcFormat)); + if (err) { + AS_TRACE("Unable to set the src format\n"); + break; + } + + if (!AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &writable)) { + void *formatListData = calloc(1, formatListSize); + if (!AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatListData)) { + for (int i=0; i < formatListSize; i += sizeof(AudioFormatListItem)) { + AudioStreamBasicDescription *pasbd = (AudioStreamBasicDescription *)formatListData + i; + + if (pasbd->mFormatID == kAudioFormatMPEG4AAC_HE || + pasbd->mFormatID == kAudioFormatMPEG4AAC_HE_V2) { + THIS->m_srcFormat = *pasbd; + break; + } + } + } + + free(formatListData); + } + + THIS->m_packetDuration = THIS->m_srcFormat.mFramesPerPacket / THIS->m_srcFormat.mSampleRate; + + AS_TRACE("srcFormat, bytes per packet %i\n", (unsigned int)THIS->m_srcFormat.mBytesPerPacket); + + if (THIS->m_audioConverter) { + AudioConverterDispose(THIS->m_audioConverter); + } + + err = AudioConverterNew(&(THIS->m_srcFormat), + &(THIS->m_dstFormat), + &(THIS->m_audioConverter)); + + if (err) { + AS_WARN("Error in creating an audio converter, error %i\n", (int)err); + + THIS->m_initializationError = err; + } + + THIS->setCookiesForStream(inAudioFileStream); + + THIS->audioQueue()->init(); + break; + } + default: { + break; + } + } +} + +/* This is called by audio file stream parser when it finds packets of audio */ +void Audio_Stream::streamDataCallback(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions) +{ + AS_TRACE("%s: inNumberBytes %u, inNumberPackets %u\n", __FUNCTION__, inNumberBytes, inNumberPackets); + + Audio_Stream *THIS = static_cast(inClientData); + + if (!THIS->m_audioStreamParserRunning) { + AS_TRACE("%s: stray callback detected!\n", __PRETTY_FUNCTION__); + return; + } + + for (int i = 0; i < inNumberPackets; i++) { + /* Allocate the packet */ + UInt32 size = inPacketDescriptions[i].mDataByteSize; + queued_packet_t *packet = (queued_packet_t *)malloc(sizeof(queued_packet_t) + size); + + packet->identifier = THIS->m_packetIdentifier; + + // If the stream didn't provide bitRate (m_bitRate == 0), then let's calculate it + if (THIS->m_bitRate == 0 && THIS->m_bitrateBufferIndex < kAudioStreamBitrateBufferSize) { + // Only keep sampling for one buffer cycle; this is to keep the counters (for instance) duration + // stable. + + THIS->m_bitrateBuffer[THIS->m_bitrateBufferIndex++] = 8 * inPacketDescriptions[i].mDataByteSize / THIS->m_packetDuration; + + if (THIS->m_bitrateBufferIndex == kAudioStreamBitrateBufferSize) { + if (THIS->m_delegate) { + THIS->m_delegate->bitrateAvailable(); + } + } + } + + AS_LOCK_TRACE("streamDataCallback: lock\n"); + pthread_mutex_lock(&THIS->m_packetQueueMutex); + + /* Prepare the packet */ + packet->next = NULL; + packet->desc = inPacketDescriptions[i]; + packet->desc.mStartOffset = 0; + memcpy(packet->data, (const char *)inInputData + inPacketDescriptions[i].mStartOffset, + size); + + if (THIS->m_queuedHead == NULL) { + THIS->m_queuedHead = THIS->m_queuedTail = THIS->m_playPacket = packet; + } else { + THIS->m_queuedTail->next = packet; + THIS->m_queuedTail = packet; + } + + THIS->m_cachedDataSize += size; + + THIS->m_packetIdentifier++; + + AS_LOCK_TRACE("streamDataCallback: unlock\n"); + pthread_mutex_unlock(&THIS->m_packetQueueMutex); + } + + THIS->determineBufferingLimits(); +} + +} // namespace astreamer diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.h new file mode 100644 index 0000000..7587ae1 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/audio_stream.h @@ -0,0 +1,255 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_AUDIO_STREAM_H +#define ASTREAMER_AUDIO_STREAM_H + +#import "input_stream.h" +#include "audio_queue.h" + +#include +#include + +namespace astreamer { + +typedef struct queued_packet { + UInt64 identifier; + AudioStreamPacketDescription desc; + struct queued_packet *next; + char data[]; +} queued_packet_t; + +typedef struct { + float offset; + float timePlayed; +} AS_Playback_Position; + +enum Audio_Stream_Error { + AS_ERR_OPEN = 1, // Cannot open the audio stream + AS_ERR_STREAM_PARSE = 2, // Parse error + AS_ERR_NETWORK = 3, // Network error + AS_ERR_UNSUPPORTED_FORMAT = 4, + AS_ERR_BOUNCING = 5, + AS_ERR_TERMINATED = 6 +}; + +class Audio_Stream_Delegate; +class File_Output; + +#define kAudioStreamBitrateBufferSize 50 + +class Audio_Stream : public Input_Stream_Delegate, public Audio_Queue_Delegate { +public: + Audio_Stream_Delegate *m_delegate; + + enum State { + STOPPED, + BUFFERING, + PLAYING, + PAUSED, + SEEKING, + FAILED, + END_OF_FILE, + PLAYBACK_COMPLETED + }; + + Audio_Stream(); + virtual ~Audio_Stream(); + + void open(); + void open(Input_Stream_Position *position); + void close(bool closeParser); + void pause(); + void rewind(unsigned seconds); + + void startCachedDataPlayback(); + + AS_Playback_Position playbackPosition(); + UInt64 audioDataByteCount(); + float durationInSeconds(); + void seekToOffset(float offset); + + Input_Stream_Position streamPositionForOffset(float offset); + + float currentVolume(); + void setDecoderRunState(bool decoderShouldRun); + void setVolume(float volume); + void setPlayRate(float playRate); + + void setUrl(CFURLRef url); + void setStrictContentTypeChecking(bool strictChecking); + void setDefaultContentType(CFStringRef defaultContentType); + void setSeekOffset(float offset); + void setDefaultContentLength(UInt64 defaultContentLength); + void setContentLength(UInt64 contentLength); + void setPreloading(bool preloading); + bool isPreloading(); + + void setOutputFile(CFURLRef url); + CFURLRef outputFile(); + + State state(); + + CFStringRef sourceFormatDescription(); + CFStringRef contentType(); + + CFStringRef createCacheIdentifierForURL(CFURLRef url); + + size_t cachedDataSize(); + bool strictContentTypeChecking(); + float bitrate(); + + UInt64 defaultContentLength(); + UInt64 contentLength(); + int playbackDataCount(); + + AudioQueueLevelMeterState levels(); + + /* Audio_Queue_Delegate */ + void audioQueueStateChanged(Audio_Queue::State state); + void audioQueueBuffersEmpty(); + void audioQueueInitializationFailed(); + void audioQueueFinishedPlayingPacket(); + + /* Input_Stream_Delegate */ + void streamIsReadyRead(); + void streamHasBytesAvailable(UInt8 *data, UInt32 numBytes); + void streamEndEncountered(); + void streamErrorOccurred(CFStringRef errorDesc); + void streamMetaDataAvailable(std::map metaData); + void streamMetaDataByteSizeAvailable(UInt32 sizeInBytes); + +private: + + Audio_Stream(const Audio_Stream&); + Audio_Stream& operator=(const Audio_Stream&); + + bool m_inputStreamRunning; + bool m_audioStreamParserRunning; + bool m_initialBufferingCompleted; + bool m_discontinuity; + bool m_preloading; + bool m_audioQueueConsumedPackets; + + UInt64 m_defaultContentLength; + UInt64 m_contentLength; + UInt64 m_originalContentLength; + UInt64 m_bytesReceived; + + State m_state; + Input_Stream *m_inputStream; + Audio_Queue *m_audioQueue; + + CFRunLoopTimerRef m_watchdogTimer; + CFRunLoopTimerRef m_seekTimer; + CFRunLoopTimerRef m_inputStreamTimer; + CFRunLoopTimerRef m_stateSetTimer; + CFRunLoopTimerRef m_decodeTimer; + + AudioFileStreamID m_audioFileStream; // the audio file stream parser + AudioConverterRef m_audioConverter; + AudioStreamBasicDescription m_srcFormat; + AudioStreamBasicDescription m_dstFormat; + OSStatus m_initializationError; + + UInt32 m_outputBufferSize; + UInt8 *m_outputBuffer; + + UInt64 m_packetIdentifier; + UInt64 m_playingPacketIdentifier; + UInt64 m_dataOffset; + float m_seekOffset; + size_t m_bounceCount; + CFAbsoluteTime m_firstBufferingTime; + + bool m_strictContentTypeChecking; + CFStringRef m_defaultContentType; + CFStringRef m_contentType; + + File_Output *m_fileOutput; + + CFURLRef m_outputFile; + + queued_packet_t *m_queuedHead; + queued_packet_t *m_queuedTail; + queued_packet_t *m_playPacket; + + std::list m_processedPackets; + + unsigned m_numPacketsToRewind; + + size_t m_cachedDataSize; + + UInt64 m_audioDataByteCount; + UInt64 m_audioDataPacketCount; + UInt32 m_bitRate; + UInt32 m_metaDataSizeInBytes; + + double m_packetDuration; + double m_bitrateBuffer[kAudioStreamBitrateBufferSize]; + size_t m_bitrateBufferIndex; + + float m_outputVolume; + + bool m_converterRunOutOfData; + bool m_decoderShouldRun; + bool m_decoderFailed; + bool m_decoderThreadCreated; + + pthread_mutex_t m_packetQueueMutex; + pthread_mutex_t m_streamStateMutex; + + pthread_t m_decodeThread; + + CFRunLoopRef m_decodeRunLoop; + CFRunLoopRef m_mainRunLoop; + + CFStringRef createHashForString(CFStringRef str); + + Audio_Queue *audioQueue(); + void closeAudioQueue(); + + void closeAndSignalError(int error, CFStringRef errorDescription); + void setState(State state); + void setCookiesForStream(AudioFileStreamID inAudioFileStream); + + void createWatchdogTimer(); + void invalidateWatchdogTimer(); + + int cachedDataCount(); + void determineBufferingLimits(); + void cleanupCachedData(); + + static void watchdogTimerCallback(CFRunLoopTimerRef timer, void *info); + static void seekTimerCallback(CFRunLoopTimerRef timer, void *info); + static void inputStreamTimerCallback(CFRunLoopTimerRef timer, void *info); + static void stateSetTimerCallback(CFRunLoopTimerRef timer, void *info); + + bool decoderShouldRun(); + static void decodeSinglePacket(CFRunLoopTimerRef timer, void *info); + static void *decodeLoop(void *arg); + + static OSStatus encoderDataCallback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData); + static void propertyValueCallback(void *inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioFlags); + static void streamDataCallback(void *inClientData, UInt32 inNumberBytes, UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions); + + AudioFileTypeID audioStreamTypeFromContentType(CFStringRef contentType); +}; + +class Audio_Stream_Delegate { +public: + virtual void audioStreamStateChanged(Audio_Stream::State state) = 0; + virtual void audioStreamErrorOccurred(int errorCode, CFStringRef errorDescription) = 0; + virtual void audioStreamMetaDataAvailable(std::map metaData) = 0; + virtual void samplesAvailable(AudioBufferList *samples, UInt32 frames, AudioStreamPacketDescription description) = 0; + virtual void bitrateAvailable() = 0; +}; + +} // namespace astreamer + +#endif // ASTREAMER_AUDIO_STREAM_H diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.cpp new file mode 100644 index 0000000..c8f6ffc --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.cpp @@ -0,0 +1,440 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "caching_stream.h" +#include "file_output.h" +#include "stream_configuration.h" +#include "file_stream.h" + +//#define CS_DEBUG 1 + +#if !defined (CS_DEBUG) +#define CS_TRACE(...) do {} while (0) +#define CS_TRACE_CFSTRING(X) do {} while (0) +#define CS_TRACE_CFURL(X) do {} while (0) +#else +#define CS_TRACE(...) printf(__VA_ARGS__) +#define CS_TRACE_CFSTRING(X) CS_TRACE("%s\n", CFStringGetCStringPtr(X, kCFStringEncodingMacRoman)) +#define CS_TRACE_CFURL(X) CS_TRACE_CFSTRING(CFURLGetString(X)) +#endif + +namespace astreamer { + +Caching_Stream::Caching_Stream(Input_Stream *target) : + m_target(target), + m_fileOutput(0), + m_fileStream(new File_Stream()), + m_cacheable(false), + m_writable(false), + m_useCache(false), + m_cacheMetaDataWritten(false), + m_cacheIdentifier(0), + m_fileUrl(0), + m_metaDataUrl(0) +{ + m_target->m_delegate = this; + m_fileStream->m_delegate = this; +} + +Caching_Stream::~Caching_Stream() +{ + if (m_target) { + delete m_target; + m_target = 0; + } + if (m_fileOutput) { + delete m_fileOutput; + m_fileOutput = 0; + } + if (m_fileStream) { + delete m_fileStream; + m_fileStream = 0; + } + if (m_cacheIdentifier) { + CFRelease(m_cacheIdentifier); + m_cacheIdentifier = 0; + } + if (m_fileUrl) { + CFRelease(m_fileUrl); + m_fileUrl = 0; + } + if (m_metaDataUrl) { + CFRelease(m_metaDataUrl); + m_fileUrl = 0; + } +} + +CFURLRef Caching_Stream::createFileURLWithPath(CFStringRef path) +{ + CFURLRef fileUrl = NULL; + + if (!path) { + return fileUrl; + } + + CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, path, NULL, NULL, kCFStringEncodingUTF8); + + CFURLRef regularUrl = CFURLCreateWithString(kCFAllocatorDefault, (escapedPath ? escapedPath : path), NULL); + + if (regularUrl) { + fileUrl = CFURLCreateFilePathURL(kCFAllocatorDefault, regularUrl, NULL); + + CFRelease(regularUrl); + } + + if (escapedPath) { + CFRelease(escapedPath); + } + + return fileUrl; +} + +void Caching_Stream::readMetaData() +{ + if (!m_metaDataUrl) { + return; + } + + CFReadStreamRef readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, m_metaDataUrl); + + if (readStream) { + if (CFReadStreamOpen(readStream)) { + + UInt8 buf[1024]; + + CFIndex bytesRead = CFReadStreamRead(readStream, buf, 1024); + + if (bytesRead > 0) { + CFStringRef contentType = CFStringCreateWithBytes(kCFAllocatorDefault, buf, bytesRead, kCFStringEncodingUTF8, false); + + if (contentType) { + if (m_fileStream) { + CS_TRACE("Setting the content type of the file stream based on the meta data\n"); + CS_TRACE_CFSTRING(contentType); + + m_fileStream->setContentType(contentType); + } + + CFRelease(contentType); + } + } + + CFReadStreamClose(readStream); + } + + CFRelease(readStream); + } +} + +Input_Stream_Position Caching_Stream::position() +{ + if (m_useCache) { + return m_fileStream->position(); + } else { + return m_target->position(); + } +} + +CFStringRef Caching_Stream::contentType() +{ + if (m_useCache) { + return m_fileStream->contentType(); + } else { + return m_target->contentType(); + } +} + +size_t Caching_Stream::contentLength() +{ + if (m_useCache) { + return m_fileStream->contentLength(); + } else { + return m_target->contentLength(); + } +} + +bool Caching_Stream::open() +{ + bool status; + + if (CFURLResourceIsReachable(m_metaDataUrl, NULL) && + CFURLResourceIsReachable(m_fileUrl, NULL)) { + m_cacheable = false; + m_writable = false; + m_useCache = true; + m_cacheMetaDataWritten = false; + + readMetaData(); + + CS_TRACE("Playing file from cache\n"); + CS_TRACE_CFURL(m_fileUrl); + + status = m_fileStream->open(); + } else { + m_cacheable = true; + m_writable = false; + m_useCache = false; + m_cacheMetaDataWritten = false; + + CS_TRACE("File not cached\n"); + + status = m_target->open(); + } + + return status; +} + +bool Caching_Stream::open(const Input_Stream_Position& position) +{ + bool status; + + if (CFURLResourceIsReachable(m_metaDataUrl, NULL) && + CFURLResourceIsReachable(m_fileUrl, NULL)) { + m_cacheable = false; + m_writable = false; + m_useCache = true; + m_cacheMetaDataWritten = false; + + readMetaData(); + + CS_TRACE("Playing file from cache\n"); + CS_TRACE_CFURL(m_fileUrl); + + status = m_fileStream->open(position); + } else { + m_cacheable = false; + m_writable = false; + m_useCache = false; + m_cacheMetaDataWritten = false; + + CS_TRACE("File not cached\n"); + + status = m_target->open(position); + } + + return status; +} + +void Caching_Stream::close() +{ + m_fileStream->close(); + m_target->close(); +} + +void Caching_Stream::setScheduledInRunLoop(bool scheduledInRunLoop) +{ + if (m_useCache) { + m_fileStream->setScheduledInRunLoop(scheduledInRunLoop); + } else { + m_target->setScheduledInRunLoop(scheduledInRunLoop); + } +} + +void Caching_Stream::setUrl(CFURLRef url) +{ + m_target->setUrl(url); +} + +void Caching_Stream::setCacheIdentifier(CFStringRef cacheIdentifier) +{ + m_cacheIdentifier = CFStringCreateCopy(kCFAllocatorDefault, cacheIdentifier); + + if (m_fileOutput) { + delete m_fileOutput; + m_fileOutput = 0; + } + + Stream_Configuration *config = Stream_Configuration::configuration(); + + CFStringRef filePath = CFStringCreateWithFormat(NULL, NULL, CFSTR("file://%@/%@"), config->cacheDirectory, m_cacheIdentifier); + CFStringRef metaDataPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("file://%@/%@.metadata"), config->cacheDirectory, m_cacheIdentifier); + + if (m_fileUrl) { + CFRelease(m_fileUrl); + m_fileUrl = 0; + } + if (m_metaDataUrl) { + CFRelease(m_metaDataUrl); + m_metaDataUrl = 0; + } + + m_fileUrl = createFileURLWithPath(filePath); + m_metaDataUrl = createFileURLWithPath(metaDataPath); + + m_fileStream->setUrl(m_fileUrl); + + CFRelease(filePath); + CFRelease(metaDataPath); +} + +bool Caching_Stream::canHandleUrl(CFURLRef url) +{ + if (!url) { + return false; + } + + CFStringRef scheme = CFURLCopyScheme(url); + + if (scheme) { + if (CFStringCompare(scheme, CFSTR("http"), 0) == kCFCompareEqualTo) { + CFRelease(scheme); + // Using cache makes only sense for HTTP + return true; + } + + CFRelease(scheme); + } + + // Nothing else to server + return false; +} + +/* ID3_Parser_Delegate */ +void Caching_Stream::id3metaDataAvailable(std::map metaData) +{ + if (m_delegate) { + m_delegate->streamMetaDataAvailable(metaData); + } +} + +void Caching_Stream::id3tagSizeAvailable(UInt32 tagSize) +{ + if (m_delegate) { + m_delegate->streamMetaDataByteSizeAvailable(tagSize); + } +} + +/* Input_Stream_Delegate */ + +void Caching_Stream::streamIsReadyRead() +{ + if (m_cacheable) { + // If the stream is cacheable (not seeked from some position) + // Check if the stream has a length. If there is no length, + // it is a continuous stream and thus cannot be cached. + m_cacheable = (m_target->contentLength() > 0); + } + +#if CS_DEBUG + if (m_cacheable) CS_TRACE("Stream can be cached!\n"); + else CS_TRACE("Stream cannot be cached\n"); +#endif + + if (m_delegate) { + m_delegate->streamIsReadyRead(); + } +} + +void Caching_Stream::streamHasBytesAvailable(UInt8 *data, UInt32 numBytes) +{ + if (m_cacheable) { + if (numBytes > 0) { + if (!m_fileOutput) { + if (m_fileUrl) { + CS_TRACE("Caching started for stream\n"); + + m_fileOutput = new File_Output(m_fileUrl); + + m_writable = true; + } + } + + if (m_writable && m_fileOutput) { + m_writable &= (m_fileOutput->write(data, numBytes) > 0); + } + } + } + if (m_delegate) { + m_delegate->streamHasBytesAvailable(data, numBytes); + } +} + +void Caching_Stream::streamEndEncountered() +{ + if (m_fileOutput) { + delete m_fileOutput; + m_fileOutput = 0; + } + + if (m_cacheable) { + if (m_writable) { + CS_TRACE("Successfully cached the stream\n"); + CS_TRACE_CFURL(m_fileUrl); + + // We only write the meta data if the stream was successfully streamed. + // In that way we can use the meta data as an indicator that there is a file to stream. + + if (!m_cacheMetaDataWritten) { + CFWriteStreamRef writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, m_metaDataUrl); + + if (writeStream) { + if (CFWriteStreamOpen(writeStream)) { + CFStringRef contentType = m_target->contentType(); + + UInt8 buf[1024]; + CFIndex usedBytes = 0; + + if (contentType) { + // It is possible that some streams don't provide a content type + CFStringGetBytes(contentType, + CFRangeMake(0, CFStringGetLength(contentType)), + kCFStringEncodingUTF8, + '?', + false, + buf, + 1024, + &usedBytes); + } + + if (usedBytes > 0) { + CS_TRACE("Writing the meta data\n"); + CS_TRACE_CFSTRING(contentType); + + CFWriteStreamWrite(writeStream, buf, usedBytes); + } + + CFWriteStreamClose(writeStream); + } + + CFRelease(writeStream); + } + + m_cacheable = false; + m_writable = false; + m_useCache = true; + m_cacheMetaDataWritten = true; + } + } + } + if (m_delegate) { + m_delegate->streamEndEncountered(); + } +} + +void Caching_Stream::streamErrorOccurred(CFStringRef errorDesc) +{ + if (m_delegate) { + m_delegate->streamErrorOccurred(errorDesc); + } +} + +void Caching_Stream::streamMetaDataAvailable(std::map metaData) +{ + if (m_delegate) { + m_delegate->streamMetaDataAvailable(metaData); + } +} + +void Caching_Stream::streamMetaDataByteSizeAvailable(UInt32 sizeInBytes) +{ + if (m_delegate) { + m_delegate->streamMetaDataByteSizeAvailable(sizeInBytes); + } +} + +} // namespace astreamer diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.h new file mode 100644 index 0000000..91f2623 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/caching_stream.h @@ -0,0 +1,73 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_CACHING_STREAM_H +#define ASTREAMER_CACHING_STREAM_H + +#include "input_stream.h" + +namespace astreamer { + +class File_Output; +class File_Stream; + +class Caching_Stream : public Input_Stream, public Input_Stream_Delegate { +private: + Input_Stream *m_target; + File_Output *m_fileOutput; + File_Stream *m_fileStream; + bool m_cacheable; + bool m_writable; + bool m_useCache; + bool m_cacheMetaDataWritten; + CFStringRef m_cacheIdentifier; + CFURLRef m_fileUrl; + CFURLRef m_metaDataUrl; + +private: + CFURLRef createFileURLWithPath(CFStringRef path); + + void readMetaData(); + +public: + Caching_Stream(Input_Stream *target); + virtual ~Caching_Stream(); + + Input_Stream_Position position(); + + CFStringRef contentType(); + size_t contentLength(); + + bool open(); + bool open(const Input_Stream_Position& position); + void close(); + + void setScheduledInRunLoop(bool scheduledInRunLoop); + + void setUrl(CFURLRef url); + + void setCacheIdentifier(CFStringRef cacheIdentifier); + + static bool canHandleUrl(CFURLRef url); + + /* ID3_Parser_Delegate */ + void id3metaDataAvailable(std::map metaData); + void id3tagSizeAvailable(UInt32 tagSize); + + void streamIsReadyRead(); + void streamHasBytesAvailable(UInt8 *data, UInt32 numBytes); + void streamEndEncountered(); + void streamErrorOccurred(CFStringRef errorDesc); + void streamMetaDataAvailable(std::map metaData); + void streamMetaDataByteSizeAvailable(UInt32 sizeInBytes); +}; + + +} // namespace astreamer + +#endif /* ASTREAMER_CACHING_STREAM_H */ diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.cpp new file mode 100644 index 0000000..c62b812 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.cpp @@ -0,0 +1,30 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "file_output.h" + +namespace astreamer { + +File_Output::File_Output(CFURLRef fileURL) : + m_writeStream(CFWriteStreamCreateWithFile(kCFAllocatorDefault, fileURL)) +{ + CFWriteStreamOpen(m_writeStream); +} + +File_Output::~File_Output() +{ + CFWriteStreamClose(m_writeStream); + CFRelease(m_writeStream); +} + +CFIndex File_Output::write(const UInt8 *buffer, CFIndex bufferLength) +{ + return CFWriteStreamWrite(m_writeStream, buffer, bufferLength); +} + +} // namespace astreamer diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.h new file mode 100644 index 0000000..478c497 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_output.h @@ -0,0 +1,32 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_FILE_OUTPUT_H +#define ASTREAMER_FILE_OUTPUT_H + +#import + +namespace astreamer { + +class File_Output { +private: + File_Output(const File_Output&); + File_Output& operator=(const File_Output&); + + CFWriteStreamRef m_writeStream; + +public: + File_Output(CFURLRef fileURL); + ~File_Output(); + + CFIndex write(const UInt8 *buffer, CFIndex bufferLength); +}; + +} // namespace astreamer + +#endif // ASTREAMER_FILE_OUTPUT_H diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.cpp new file mode 100644 index 0000000..acb554c --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.cpp @@ -0,0 +1,405 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "file_stream.h" +#include "stream_configuration.h" + +namespace astreamer { + +File_Stream::File_Stream() : + m_url(0), + m_readStream(0), + m_scheduledInRunLoop(false), + m_readPending(false), + m_fileReadBuffer(0), + m_id3Parser(new ID3_Parser()), + m_contentType(0) +{ + m_id3Parser->m_delegate = this; +} + +File_Stream::~File_Stream() +{ + close(); + + if (m_fileReadBuffer) { + delete [] m_fileReadBuffer; + m_fileReadBuffer = 0; + } + + if (m_url) { + CFRelease(m_url); + m_url = 0; + } + + delete m_id3Parser; + m_id3Parser = 0; + + if (m_contentType) { + CFRelease(m_contentType); + } +} + +Input_Stream_Position File_Stream::position() +{ + return m_position; +} + +CFStringRef File_Stream::contentType() +{ + if (m_contentType) { + // Use the provided content type + return m_contentType; + } + + // Try to resolve the content type from the file + + CFStringRef contentType = CFSTR(""); + CFStringRef pathComponent = 0; + CFIndex len = 0; + CFRange range; + CFStringRef suffix = 0; + + if (!m_url) { + goto done; + } + + pathComponent = CFURLCopyLastPathComponent(m_url); + + if (!pathComponent) { + goto done; + } + + len = CFStringGetLength(pathComponent); + + if (len > 5) { + range.length = 4; + range.location = len - 4; + + suffix = CFStringCreateWithSubstring(kCFAllocatorDefault, + pathComponent, + range); + + if (!suffix) { + goto done; + } + + // TODO: we should do the content-type resolvation in a better way. + if (CFStringCompare(suffix, CFSTR(".mp3"), 0) == kCFCompareEqualTo) { + contentType = CFSTR("audio/mpeg"); + } else if (CFStringCompare(suffix, CFSTR(".m4a"), 0) == kCFCompareEqualTo) { + contentType = CFSTR("audio/x-m4a"); + } else if (CFStringCompare(suffix, CFSTR(".mp4"), 0) == kCFCompareEqualTo) { + contentType = CFSTR("audio/mp4"); + } else if (CFStringCompare(suffix, CFSTR(".aac"), 0) == kCFCompareEqualTo) { + contentType = CFSTR("audio/aac"); + } + } + +done: + if (pathComponent) { + CFRelease(pathComponent); + } + if (suffix) { + CFRelease(suffix); + } + + return contentType; +} + +void File_Stream::setContentType(CFStringRef contentType) +{ + if (m_contentType) { + CFRelease(m_contentType); + m_contentType = 0; + } + if (contentType) { + m_contentType = CFStringCreateCopy(kCFAllocatorDefault, contentType); + } +} + +size_t File_Stream::contentLength() +{ + CFNumberRef length = NULL; + CFErrorRef err = NULL; + + if (CFURLCopyResourcePropertyForKey(m_url, kCFURLFileSizeKey, &length, &err)) { + CFIndex fileLength; + if (CFNumberGetValue(length, kCFNumberCFIndexType, &fileLength)) { + CFRelease(length); + + return fileLength; + } + } + return 0; +} + +bool File_Stream::open() +{ + Input_Stream_Position position; + position.start = 0; + position.end = 0; + + m_id3Parser->reset(); + + return open(position); +} + +bool File_Stream::open(const Input_Stream_Position& position) +{ + bool success = false; + CFStreamClientContext CTX = { 0, this, NULL, NULL, NULL }; + + /* Already opened a read stream, return */ + if (m_readStream) { + goto out; + } + + if (!m_url) { + goto out; + } + + /* Reset state */ + m_position = position; + + m_readPending = false; + + /* Failed to create a stream */ + if (!(m_readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, m_url))) { + goto out; + } + + if (m_position.start > 0) { + CFNumberRef position = CFNumberCreate(0, kCFNumberLongLongType, &m_position.start); + CFReadStreamSetProperty(m_readStream, kCFStreamPropertyFileCurrentOffset, position); + CFRelease(position); + } + + if (!CFReadStreamSetClient(m_readStream, kCFStreamEventHasBytesAvailable | + kCFStreamEventEndEncountered | + kCFStreamEventErrorOccurred, readCallBack, &CTX)) { + CFRelease(m_readStream); + m_readStream = 0; + goto out; + } + + setScheduledInRunLoop(true); + + if (!CFReadStreamOpen(m_readStream)) { + /* Open failed: clean */ + CFReadStreamSetClient(m_readStream, 0, NULL, NULL); + setScheduledInRunLoop(false); + if (m_readStream) { + CFRelease(m_readStream); + m_readStream = 0; + } + goto out; + } + + success = true; + +out: + + if (success) { + if (m_delegate) { + m_delegate->streamIsReadyRead(); + } + } + return success; +} + +void File_Stream::close() +{ + /* The stream has been already closed */ + if (!m_readStream) { + return; + } + + CFReadStreamSetClient(m_readStream, 0, NULL, NULL); + setScheduledInRunLoop(false); + CFReadStreamClose(m_readStream); + CFRelease(m_readStream); + m_readStream = 0; +} + +void File_Stream::setScheduledInRunLoop(bool scheduledInRunLoop) +{ + /* The stream has not been opened, or it has been already closed */ + if (!m_readStream) { + return; + } + + /* The state doesn't change */ + if (m_scheduledInRunLoop == scheduledInRunLoop) { + return; + } + + if (m_scheduledInRunLoop) { + CFReadStreamUnscheduleFromRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + } else { + if (m_readPending) { + m_readPending = false; + + readCallBack(m_readStream, kCFStreamEventHasBytesAvailable, this); + } + + CFReadStreamScheduleWithRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + } + + m_scheduledInRunLoop = scheduledInRunLoop; +} + +void File_Stream::setUrl(CFURLRef url) +{ + if (m_url) { + CFRelease(m_url); + } + if (url) { + m_url = (CFURLRef)CFRetain(url); + } else { + m_url = NULL; + } +} + +bool File_Stream::canHandleUrl(CFURLRef url) +{ + if (!url) { + return false; + } + + CFStringRef scheme = CFURLCopyScheme(url); + + if (scheme) { + if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) { + CFRelease(scheme); + // The only scheme we claim to handle are the local files + return true; + } + + CFRelease(scheme); + } + + // We don't handle anything else but local files + return false; +} + +/* ID3_Parser_Delegate */ +void File_Stream::id3metaDataAvailable(std::map metaData) +{ + if (m_delegate) { + m_delegate->streamMetaDataAvailable(metaData); + } +} + +void File_Stream::id3tagSizeAvailable(UInt32 tagSize) +{ + if (m_delegate) { + m_delegate->streamMetaDataByteSizeAvailable(tagSize); + } +} + +void File_Stream::readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo) +{ + File_Stream *THIS = static_cast(clientCallBackInfo); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + switch (eventType) { + case kCFStreamEventHasBytesAvailable: { + if (!THIS->m_fileReadBuffer) { + THIS->m_fileReadBuffer = new UInt8[config->httpConnectionBufferSize]; + } + + while (CFReadStreamHasBytesAvailable(stream)) { + if (!THIS->m_scheduledInRunLoop) { + /* + * This is critical - though the stream has data available, + * do not try to feed the audio queue with data, if it has + * indicated that it doesn't want more data due to buffers + * full. + */ + THIS->m_readPending = true; + break; + } + + CFIndex bytesRead = CFReadStreamRead(stream, THIS->m_fileReadBuffer, config->httpConnectionBufferSize); + + if (CFReadStreamGetStatus(stream) == kCFStreamStatusError || + bytesRead < 0) { + + if (THIS->m_delegate) { + CFStringRef reportedNetworkError = NULL; + CFErrorRef streamError = CFReadStreamCopyError(stream); + + if (streamError) { + CFStringRef errorDesc = CFErrorCopyDescription(streamError); + + if (errorDesc) { + reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc); + + CFRelease(errorDesc); + } + + CFRelease(streamError); + } + + THIS->m_delegate->streamErrorOccurred(reportedNetworkError); + if (reportedNetworkError) { + CFRelease(reportedNetworkError); + } + } + break; + } + + if (bytesRead > 0) { + if (THIS->m_delegate) { + THIS->m_delegate->streamHasBytesAvailable(THIS->m_fileReadBuffer, (UInt32)bytesRead); + } + + if (THIS->m_id3Parser->wantData()) { + THIS->m_id3Parser->feedData(THIS->m_fileReadBuffer, (UInt32)bytesRead); + } + } + } + + break; + } + case kCFStreamEventEndEncountered: { + if (THIS->m_delegate) { + THIS->m_delegate->streamEndEncountered(); + } + break; + } + case kCFStreamEventErrorOccurred: { + if (THIS->m_delegate) { + CFStringRef reportedNetworkError = NULL; + CFErrorRef streamError = CFReadStreamCopyError(stream); + + if (streamError) { + CFStringRef errorDesc = CFErrorCopyDescription(streamError); + + if (errorDesc) { + reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc); + + CFRelease(errorDesc); + } + + CFRelease(streamError); + } + + THIS->m_delegate->streamErrorOccurred(reportedNetworkError); + if (reportedNetworkError) { + CFRelease(reportedNetworkError); + } + } + break; + } + } +} + +} // namespace astreamer diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.h new file mode 100644 index 0000000..77eb73e --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/file_stream.h @@ -0,0 +1,64 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_FILE_STREAM_H +#define ASTREAMER_FILE_STREAM_H + +#import "input_stream.h" +#import "id3_parser.h" + +namespace astreamer { + +class File_Stream : public Input_Stream { +private: + + File_Stream(const File_Stream&); + File_Stream& operator=(const File_Stream&); + + CFURLRef m_url; + CFReadStreamRef m_readStream; + bool m_scheduledInRunLoop; + bool m_readPending; + Input_Stream_Position m_position; + + UInt8 *m_fileReadBuffer; + + ID3_Parser *m_id3Parser; + + CFStringRef m_contentType; + + static void readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo); + +public: + File_Stream(); + virtual ~File_Stream(); + + Input_Stream_Position position(); + + CFStringRef contentType(); + void setContentType(CFStringRef contentType); + size_t contentLength(); + + bool open(); + bool open(const Input_Stream_Position& position); + void close(); + + void setScheduledInRunLoop(bool scheduledInRunLoop); + + void setUrl(CFURLRef url); + + static bool canHandleUrl(CFURLRef url); + + /* ID3_Parser_Delegate */ + void id3metaDataAvailable(std::map metaData); + void id3tagSizeAvailable(UInt32 tagSize); +}; + +} // namespace astreamer + +#endif // ASTREAMER_FILE_STREAM_H diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.cpp new file mode 100644 index 0000000..28c955b --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.cpp @@ -0,0 +1,908 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "http_stream.h" +#include "audio_queue.h" +#include "id3_parser.h" +#include "stream_configuration.h" + +//#define HS_DEBUG 1 + +#if !defined (HS_DEBUG) +#define HS_TRACE(...) do {} while (0) +#define HS_TRACE_CFSTRING(X) do {} while (0) +#else +#define HS_TRACE(...) printf(__VA_ARGS__) +#define HS_TRACE_CFSTRING(X) HS_TRACE("%s\n", CFStringGetCStringPtr(X, kCFStringEncodingMacRoman)) +#endif + +/* + * Comment the following line to disable ID3 tag support: + */ +#define INCLUDE_ID3TAG_SUPPORT 1 + +namespace astreamer { + +CFStringRef HTTP_Stream::httpRequestMethod = CFSTR("GET"); +CFStringRef HTTP_Stream::httpUserAgentHeader = CFSTR("User-Agent"); +CFStringRef HTTP_Stream::httpRangeHeader = CFSTR("Range"); +CFStringRef HTTP_Stream::icyMetaDataHeader = CFSTR("Icy-MetaData"); +CFStringRef HTTP_Stream::icyMetaDataValue = CFSTR("1"); /* always request ICY metadata, if available */ + + +/* HTTP_Stream: public */ +HTTP_Stream::HTTP_Stream() : + m_readStream(0), + m_scheduledInRunLoop(false), + m_readPending(false), + m_url(0), + m_httpHeadersParsed(false), + m_contentType(0), + m_contentLength(0), + m_bytesRead(0), + + m_icyStream(false), + m_icyHeaderCR(false), + m_icyHeadersRead(false), + m_icyHeadersParsed(false), + + m_icyName(0), + + m_icyMetaDataInterval(0), + m_dataByteReadCount(0), + m_metaDataBytesRemaining(0), + + m_httpReadBuffer(0), + m_icyReadBuffer(0), + + m_id3Parser(new ID3_Parser()) +{ + m_id3Parser->m_delegate = this; +} + +HTTP_Stream::~HTTP_Stream() +{ + close(); + + for (std::vector::iterator h = m_icyHeaderLines.begin(); h != m_icyHeaderLines.end(); ++h) { + CFRelease(*h); + } + + m_icyHeaderLines.clear(); + + if (m_contentType) { + CFRelease(m_contentType); + m_contentType = 0; + } + + if (m_icyName) { + CFRelease(m_icyName); + m_icyName = 0; + } + + if (m_httpReadBuffer) { + delete [] m_httpReadBuffer; + m_httpReadBuffer = 0; + } + if (m_icyReadBuffer) { + delete [] m_icyReadBuffer; + m_icyReadBuffer = 0; + } + if (m_url) { + CFRelease(m_url); + m_url = 0; + } + + delete m_id3Parser; + m_id3Parser = 0; +} + +Input_Stream_Position HTTP_Stream::position() +{ + return m_position; +} + +CFStringRef HTTP_Stream::contentType() +{ + return m_contentType; +} + +size_t HTTP_Stream::contentLength() +{ + return m_contentLength; +} + +bool HTTP_Stream::open() +{ + Input_Stream_Position position; + position.start = 0; + position.end = 0; + + m_contentLength = 0; +#ifdef INCLUDE_ID3TAG_SUPPORT + m_id3Parser->reset(); +#endif + + return open(position); +} + +bool HTTP_Stream::open(const Input_Stream_Position& position) +{ + bool success = false; + CFStreamClientContext CTX = { 0, this, NULL, NULL, NULL }; + + /* Already opened a read stream, return */ + if (m_readStream) { + goto out; + } + + /* Reset state */ + m_position = position; + + m_readPending = false; + m_httpHeadersParsed = false; + + if (m_contentType) { + CFRelease(m_contentType); + m_contentType = NULL; + } + + m_icyStream = false; + m_icyHeaderCR = false; + m_icyHeadersRead = false; + m_icyHeadersParsed = false; + + if (m_icyName) { + CFRelease(m_icyName); + m_icyName = 0; + } + + for (std::vector::iterator h = m_icyHeaderLines.begin(); h != m_icyHeaderLines.end(); ++h) { + CFRelease(*h); + } + + m_icyHeaderLines.clear(); + m_icyMetaDataInterval = 0; + m_dataByteReadCount = 0; + m_metaDataBytesRemaining = 0; + m_bytesRead = 0; + + if (!m_url) { + goto out; + } + + /* Failed to create a stream */ + if (!(m_readStream = createReadStream(m_url))) { + goto out; + } + + if (!CFReadStreamSetClient(m_readStream, kCFStreamEventHasBytesAvailable | + kCFStreamEventEndEncountered | + kCFStreamEventErrorOccurred, readCallBack, &CTX)) { + CFRelease(m_readStream); + m_readStream = 0; + goto out; + } + + setScheduledInRunLoop(true); + + if (!CFReadStreamOpen(m_readStream)) { + /* Open failed: clean */ + CFReadStreamSetClient(m_readStream, 0, NULL, NULL); + setScheduledInRunLoop(false); + if (m_readStream) { + CFRelease(m_readStream); + m_readStream = 0; + } + goto out; + } + + success = true; + +out: + return success; +} + +void HTTP_Stream::close() +{ + /* The stream has been already closed */ + if (!m_readStream) { + return; + } + + CFReadStreamSetClient(m_readStream, 0, NULL, NULL); + setScheduledInRunLoop(false); + CFReadStreamClose(m_readStream); + CFRelease(m_readStream); + m_readStream = 0; +} + +void HTTP_Stream::setScheduledInRunLoop(bool scheduledInRunLoop) +{ + /* The stream has not been opened, or it has been already closed */ + if (!m_readStream) { + return; + } + + /* The state doesn't change */ + if (m_scheduledInRunLoop == scheduledInRunLoop) { + return; + } + + if (m_scheduledInRunLoop) { + CFReadStreamUnscheduleFromRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + } else { + if (m_readPending) { + m_readPending = false; + + readCallBack(m_readStream, kCFStreamEventHasBytesAvailable, this); + } + + CFReadStreamScheduleWithRunLoop(m_readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); + } + + m_scheduledInRunLoop = scheduledInRunLoop; +} + +void HTTP_Stream::setUrl(CFURLRef url) +{ + if (m_url) { + CFRelease(m_url); + } + if (url) { + m_url = (CFURLRef)CFRetain(url); + } else { + m_url = NULL; + } +} + +bool HTTP_Stream::canHandleUrl(CFURLRef url) +{ + if (!url) { + return false; + } + + CFStringRef scheme = CFURLCopyScheme(url); + + if (scheme) { + if (CFStringCompare(scheme, CFSTR("file"), 0) == kCFCompareEqualTo) { + CFRelease(scheme); + + // The only scheme we claim not to handle are local files. + return false; + } + + CFRelease(scheme); + } + + return true; +} + +void HTTP_Stream::id3metaDataAvailable(std::map metaData) +{ + if (m_delegate) { + m_delegate->streamMetaDataAvailable(metaData); + } +} + +void HTTP_Stream::id3tagSizeAvailable(UInt32 tagSize) +{ + if (m_delegate) { + m_delegate->streamMetaDataByteSizeAvailable(tagSize); + } +} + +/* private */ + +CFReadStreamRef HTTP_Stream::createReadStream(CFURLRef url) +{ + CFReadStreamRef readStream = 0; + CFHTTPMessageRef request = 0; + CFDictionaryRef proxySettings = 0; + + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (!(request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, httpRequestMethod, url, kCFHTTPVersion1_1))) { + goto out; + } + + if (config->userAgent) { + CFHTTPMessageSetHeaderFieldValue(request, httpUserAgentHeader, config->userAgent); + } + + CFHTTPMessageSetHeaderFieldValue(request, icyMetaDataHeader, icyMetaDataValue); + + if (m_position.start > 0 && m_position.end > m_position.start) { + CFStringRef rangeHeaderValue = CFStringCreateWithFormat(NULL, + NULL, + CFSTR("bytes=%llu-%llu"), + m_position.start, + m_position.end); + + CFHTTPMessageSetHeaderFieldValue(request, httpRangeHeader, rangeHeaderValue); + CFRelease(rangeHeaderValue); + } else if (m_position.start > 0 && m_position.end < m_position.start) { + CFStringRef rangeHeaderValue = CFStringCreateWithFormat(NULL, + NULL, + CFSTR("bytes=%llu-"), + m_position.start); + CFHTTPMessageSetHeaderFieldValue(request, httpRangeHeader, rangeHeaderValue); + CFRelease(rangeHeaderValue); + } + + + if (config->predefinedHttpHeaderValues) { + const CFIndex numKeys = CFDictionaryGetCount(config->predefinedHttpHeaderValues); + + if (numKeys > 0) { + CFTypeRef *keys = (CFTypeRef *) malloc(numKeys * sizeof(CFTypeRef)); + + if (keys) { + CFDictionaryGetKeysAndValues(config->predefinedHttpHeaderValues, (const void **) keys, NULL); + + for (CFIndex i=0; i < numKeys; i++) { + CFTypeRef key = keys[i]; + + if (CFGetTypeID(key) == CFStringGetTypeID()) { + const void *value = CFDictionaryGetValue(config->predefinedHttpHeaderValues, (const void *) key); + + if (value) { + CFStringRef headerKey = (CFStringRef) key; + + CFTypeRef valueRef = (CFTypeRef) value; + + if (CFGetTypeID(valueRef) == CFStringGetTypeID()) { + CFStringRef headerValue = (CFStringRef) valueRef; + + HS_TRACE("Setting predefined HTTP header "); + HS_TRACE_CFSTRING(headerKey); + HS_TRACE_CFSTRING(headerValue); + + CFHTTPMessageSetHeaderFieldValue(request, headerKey, headerValue); + } + } + } + } + + free(keys); + } + } + } + + if (!(readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request))) { + goto out; + } + + CFReadStreamSetProperty(readStream, + kCFStreamNetworkServiceType, + kCFStreamNetworkServiceTypeBackground); + + CFReadStreamSetProperty(readStream, + kCFStreamPropertyHTTPShouldAutoredirect, + kCFBooleanTrue); + + proxySettings = CFNetworkCopySystemProxySettings(); + if (proxySettings) { + CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxySettings); + CFRelease(proxySettings); + } + +out: + if (request) { + CFRelease(request); + } + + return readStream; +} + +void HTTP_Stream::parseHttpHeadersIfNeeded(const UInt8 *buf, const CFIndex bufSize) +{ + if (m_httpHeadersParsed) { + return; + } + m_httpHeadersParsed = true; + + /* If the response has the "ICY 200 OK" string, + * we are dealing with the ShoutCast protocol. + * The HTTP headers won't be available. + */ + if (bufSize >= 10 && + buf[0] == 0x49 && buf[1] == 0x43 && buf[2] == 0x59 && + buf[3] == 0x20 && buf[4] == 0x32 && buf[5] == 0x30 && + buf[6] == 0x30 && buf[7] == 0x20 && buf[8] == 0x4F && + buf[9] == 0x4B) { + m_icyStream = true; + + HS_TRACE("Detected an IceCast stream\n"); + + // This is an ICY stream, don't try to parse the HTTP headers + return; + } + + HS_TRACE("A regular HTTP stream\n"); + + CFHTTPMessageRef response = (CFHTTPMessageRef)CFReadStreamCopyProperty(m_readStream, kCFStreamPropertyHTTPResponseHeader); + CFIndex statusCode = 0; + + if (response) { + /* + * If the server responded with the icy-metaint header, the response + * body will be encoded in the ShoutCast protocol. + */ + CFStringRef icyMetaIntString = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("icy-metaint")); + if (icyMetaIntString) { + m_icyStream = true; + m_icyHeadersParsed = true; + m_icyHeadersRead = true; + m_icyMetaDataInterval = CFStringGetIntValue(icyMetaIntString); + CFRelease(icyMetaIntString); + } + + HS_TRACE("icy-metaint: %zu\n", m_icyMetaDataInterval); + + statusCode = CFHTTPMessageGetResponseStatusCode(response); + + HS_TRACE("HTTP response code %zu", statusCode); + + CFStringRef icyNameString = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("icy-name")); + if (icyNameString) { + if (m_icyName) { + CFRelease(m_icyName); + } + m_icyName = icyNameString; + + if (m_delegate) { + std::map metadataMap; + + metadataMap[CFSTR("IcecastStationName")] = CFStringCreateCopy(kCFAllocatorDefault, m_icyName); + + m_delegate->streamMetaDataAvailable(metadataMap); + } + } + + if (m_contentType) { + CFRelease(m_contentType); + } + + m_contentType = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Content-Type")); + + HS_TRACE("Content-type: "); + HS_TRACE_CFSTRING(m_contentType); + + CFStringRef contentLengthString = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Content-Length")); + if (contentLengthString) { + m_contentLength = CFStringGetIntValue(contentLengthString); + + CFRelease(contentLengthString); + } + + CFRelease(response); + } + + if (m_delegate && + (statusCode == 200 || statusCode == 206)) { + m_delegate->streamIsReadyRead(); + } else { + if (m_delegate) { + CFStringRef statusCodeString = CFStringCreateWithFormat(NULL, + NULL, + CFSTR("HTTP response code %d"), + (unsigned int)statusCode); + m_delegate->streamErrorOccurred(statusCodeString); + + if (statusCodeString) { + CFRelease(statusCodeString); + } + } + } +} + +void HTTP_Stream::parseICYStream(const UInt8 *buf, const CFIndex bufSize) +{ + HS_TRACE("Parsing an IceCast stream, received %li bytes\n", bufSize); + + CFIndex offset = 0; + CFIndex bytesFound = 0; + if (!m_icyHeadersRead) { + HS_TRACE("ICY headers not read, reading\n"); + + for (; offset < bufSize; offset++) { + if (m_icyHeaderCR && buf[offset] == '\n') { + if (bytesFound > 0) { + m_icyHeaderLines.push_back(createMetaDataStringWithMostReasonableEncoding(&buf[offset-bytesFound-1], bytesFound)); + + bytesFound = 0; + + HS_TRACE_CFSTRING(m_icyHeaderLines[m_icyHeaderLines.size()-1]); + + continue; + } + + HS_TRACE("End of ICY headers\n"); + + m_icyHeadersRead = true; + break; + } + + if (buf[offset] == '\r') { + m_icyHeaderCR = true; + continue; + } else { + m_icyHeaderCR = false; + } + + bytesFound++; + } + } else if (!m_icyHeadersParsed) { + HS_TRACE("ICY headers not parsed, parsing\n"); + + const CFStringRef icyContentTypeHeader = CFSTR("content-type:"); + const CFStringRef icyMetaDataHeader = CFSTR("icy-metaint:"); + const CFStringRef icyNameHeader = CFSTR("icy-name:"); + + const CFIndex icyContenTypeHeaderLength = CFStringGetLength(icyContentTypeHeader); + const CFIndex icyMetaDataHeaderLength = CFStringGetLength(icyMetaDataHeader); + const CFIndex icyNameHeaderLength = CFStringGetLength(icyNameHeader); + + for (std::vector::iterator h = m_icyHeaderLines.begin(); h != m_icyHeaderLines.end(); ++h) { + CFStringRef line = *h; + const CFIndex lineLength = CFStringGetLength(line); + + if (lineLength == 0) { + continue; + } + + HS_TRACE_CFSTRING(line); + + if (CFStringCompareWithOptions(line, + icyContentTypeHeader, + CFRangeMake(0, icyContenTypeHeaderLength), + 0) == kCFCompareEqualTo) { + if (m_contentType) { + CFRelease(m_contentType); + m_contentType = 0; + } + m_contentType = CFStringCreateWithSubstring(kCFAllocatorDefault, + line, + CFRangeMake(icyContenTypeHeaderLength, lineLength - icyContenTypeHeaderLength)); + + } + + if (CFStringCompareWithOptions(line, + icyMetaDataHeader, + CFRangeMake(0, icyMetaDataHeaderLength), + 0) == kCFCompareEqualTo) { + CFStringRef metadataInterval = CFStringCreateWithSubstring(kCFAllocatorDefault, + line, + CFRangeMake(icyMetaDataHeaderLength, lineLength - icyMetaDataHeaderLength)); + + if (metadataInterval) { + m_icyMetaDataInterval = CFStringGetIntValue(metadataInterval); + + CFRelease(metadataInterval); + } else { + m_icyMetaDataInterval = 0; + } + } + + if (CFStringCompareWithOptions(line, + icyNameHeader, + CFRangeMake(0, icyNameHeaderLength), + 0) == kCFCompareEqualTo) { + if (m_icyName) { + CFRelease(m_icyName); + } + + m_icyName = CFStringCreateWithSubstring(kCFAllocatorDefault, + line, + CFRangeMake(icyNameHeaderLength, lineLength - icyNameHeaderLength)); + } + } + + m_icyHeadersParsed = true; + offset++; + + if (m_delegate) { + m_delegate->streamIsReadyRead(); + } + } + + Stream_Configuration *config = Stream_Configuration::configuration(); + + if (!m_icyReadBuffer) { + m_icyReadBuffer = new UInt8[config->httpConnectionBufferSize]; + } + + HS_TRACE("Reading ICY stream for playback\n"); + + UInt32 i=0; + + for (; offset < bufSize; offset++) { + // is this a metadata byte? + if (m_metaDataBytesRemaining > 0) { + m_metaDataBytesRemaining--; + + if (m_metaDataBytesRemaining == 0) { + m_dataByteReadCount = 0; + + if (m_delegate && !m_icyMetaData.empty()) { + std::map metadataMap; + + CFStringRef metaData = createMetaDataStringWithMostReasonableEncoding(&m_icyMetaData[0], + m_icyMetaData.size()); + + if (!metaData) { + // Metadata encoding failed, cannot parse. + m_icyMetaData.clear(); + continue; + } + + CFArrayRef tokens = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, + metaData, + CFSTR(";")); + + for (CFIndex i=0, max=CFArrayGetCount(tokens); i < max; i++) { + CFStringRef token = (CFStringRef) CFArrayGetValueAtIndex(tokens, i); + + CFRange foundRange; + + if (CFStringFindWithOptions(token, + CFSTR("='"), + CFRangeMake(0, CFStringGetLength(token)), + NULL, + &foundRange) == true) { + + CFRange keyRange = CFRangeMake(0, foundRange.location); + + CFStringRef metadaKey = CFStringCreateWithSubstring(kCFAllocatorDefault, + token, + keyRange); + + CFRange valueRange = CFRangeMake(foundRange.location + 2, CFStringGetLength(token) - keyRange.length - 3); + + CFStringRef metadaValue = CFStringCreateWithSubstring(kCFAllocatorDefault, + token, + valueRange); + + metadataMap[metadaKey] = metadaValue; + } + } + + CFRelease(tokens); + CFRelease(metaData); + + if (m_icyName) { + metadataMap[CFSTR("IcecastStationName")] = CFStringCreateCopy(kCFAllocatorDefault, m_icyName); + } + + m_delegate->streamMetaDataAvailable(metadataMap); + } + m_icyMetaData.clear(); + continue; + } + + m_icyMetaData.push_back(buf[offset]); + continue; + } + + // is this the interval byte? + if (m_icyMetaDataInterval > 0 && m_dataByteReadCount == m_icyMetaDataInterval) { + m_metaDataBytesRemaining = buf[offset] * 16; + + if (m_metaDataBytesRemaining == 0) { + m_dataByteReadCount = 0; + } + continue; + } + + // a data byte + m_dataByteReadCount++; + m_icyReadBuffer[i++] = buf[offset]; + } + + if (m_delegate && i > 0) { + m_delegate->streamHasBytesAvailable(m_icyReadBuffer, i); + } +} + +#define TRY_ENCODING(STR,ENC) STR = CFStringCreateWithBytes(kCFAllocatorDefault, bytes, numBytes, ENC, false); \ + if (STR != NULL) { return STR; } + +CFStringRef HTTP_Stream::createMetaDataStringWithMostReasonableEncoding(const UInt8 *bytes, const CFIndex numBytes) +{ + CFStringRef metaData; + + TRY_ENCODING(metaData, kCFStringEncodingUTF8); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin1); + TRY_ENCODING(metaData, kCFStringEncodingWindowsLatin1); + TRY_ENCODING(metaData, kCFStringEncodingNextStepLatin); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin2); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin3); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin4); + TRY_ENCODING(metaData, kCFStringEncodingISOLatinCyrillic); + TRY_ENCODING(metaData, kCFStringEncodingISOLatinArabic); + TRY_ENCODING(metaData, kCFStringEncodingISOLatinGreek); + TRY_ENCODING(metaData, kCFStringEncodingISOLatinHebrew); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin5); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin6); + TRY_ENCODING(metaData, kCFStringEncodingISOLatinThai); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin7); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin8); + TRY_ENCODING(metaData, kCFStringEncodingISOLatin9); + TRY_ENCODING(metaData, kCFStringEncodingWindowsLatin2); + TRY_ENCODING(metaData, kCFStringEncodingWindowsCyrillic); + TRY_ENCODING(metaData, kCFStringEncodingWindowsGreek); + TRY_ENCODING(metaData, kCFStringEncodingWindowsLatin5); + TRY_ENCODING(metaData, kCFStringEncodingWindowsHebrew); + TRY_ENCODING(metaData, kCFStringEncodingWindowsArabic); + TRY_ENCODING(metaData, kCFStringEncodingKOI8_R); + TRY_ENCODING(metaData, kCFStringEncodingBig5); + TRY_ENCODING(metaData, kCFStringEncodingASCII); + + return metaData; +} + +#undef TRY_ENCODING + +void HTTP_Stream::readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo) +{ + HTTP_Stream *THIS = static_cast(clientCallBackInfo); + + Stream_Configuration *config = Stream_Configuration::configuration(); + + CFStringRef reportedNetworkError = NULL; + + switch (eventType) { + case kCFStreamEventHasBytesAvailable: { + if (!THIS->m_httpReadBuffer) { + THIS->m_httpReadBuffer = new UInt8[config->httpConnectionBufferSize]; + } + + while (CFReadStreamHasBytesAvailable(stream)) { + if (!THIS->m_scheduledInRunLoop) { + /* + * This is critical - though the stream has data available, + * do not try to feed the audio queue with data, if it has + * indicated that it doesn't want more data due to buffers + * full. + */ + THIS->m_readPending = true; + break; + } + + CFIndex bytesRead = CFReadStreamRead(stream, THIS->m_httpReadBuffer, config->httpConnectionBufferSize); + + if (CFReadStreamGetStatus(stream) == kCFStreamStatusError || + bytesRead < 0) { + if (THIS->contentLength() > 0) { + /* + * Try to recover gracefully if we have a non-continuous stream + */ + Input_Stream_Position currentPosition = THIS->position(); + + Input_Stream_Position recoveryPosition; + recoveryPosition.start = currentPosition.start + THIS->m_bytesRead; + recoveryPosition.end = THIS->contentLength(); + + HS_TRACE("Recovering HTTP stream, start %llu\n", recoveryPosition.start); + + THIS->open(recoveryPosition); + + break; + } + + CFErrorRef streamError = CFReadStreamCopyError(stream); + + if (streamError) { + CFStringRef errorDesc = CFErrorCopyDescription(streamError); + + if (errorDesc) { + reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc); + + CFRelease(errorDesc); + } + + CFRelease(streamError); + } + + if (THIS->m_delegate) { + THIS->m_delegate->streamErrorOccurred(reportedNetworkError); + + if (reportedNetworkError) { + CFRelease(reportedNetworkError); + reportedNetworkError = NULL; + } + } + break; + } + + if (bytesRead > 0) { + THIS->m_bytesRead += bytesRead; + + HS_TRACE("Read %li bytes, total %llu\n", bytesRead, THIS->m_bytesRead); + + THIS->parseHttpHeadersIfNeeded(THIS->m_httpReadBuffer, bytesRead); + + #ifdef INCLUDE_ID3TAG_SUPPORT + if (!THIS->m_icyStream && THIS->m_id3Parser->wantData()) { + THIS->m_id3Parser->feedData(THIS->m_httpReadBuffer, (UInt32)bytesRead); + } + #endif + + if (THIS->m_icyStream) { + HS_TRACE("Parsing ICY stream\n"); + + THIS->parseICYStream(THIS->m_httpReadBuffer, bytesRead); + } else { + if (THIS->m_delegate) { + HS_TRACE("Not an ICY stream; calling the delegate back\n"); + + THIS->m_delegate->streamHasBytesAvailable(THIS->m_httpReadBuffer, (UInt32)bytesRead); + } + } + } + } + + if (reportedNetworkError) { + CFRelease(reportedNetworkError); + reportedNetworkError = NULL; + } + + break; + } + case kCFStreamEventEndEncountered: { + + // This should concerns only non-continous streams + if (THIS->m_bytesRead < THIS->contentLength()) { + HS_TRACE("End of stream, but we have read only %llu bytes on a total of %li. Missing: %llu\n", THIS->m_bytesRead, THIS->contentLength(), (THIS->contentLength() - THIS->m_bytesRead)); + + Input_Stream_Position currentPosition = THIS->position(); + + Input_Stream_Position recoveryPosition; + recoveryPosition.start = currentPosition.start + THIS->m_bytesRead; + recoveryPosition.end = THIS->contentLength(); + + HS_TRACE("Reopen for the end of the file from byte position: %llu\n", recoveryPosition.start); + THIS->close(); + THIS->open(recoveryPosition); + break; + } + + if (THIS->m_delegate) { + THIS->m_delegate->streamEndEncountered(); + } + break; + } + case kCFStreamEventErrorOccurred: { + if (THIS->m_delegate) { + CFStringRef reportedNetworkError = NULL; + CFErrorRef streamError = CFReadStreamCopyError(stream); + + if (streamError) { + CFStringRef errorDesc = CFErrorCopyDescription(streamError); + + if (errorDesc) { + reportedNetworkError = CFStringCreateCopy(kCFAllocatorDefault, errorDesc); + + CFRelease(errorDesc); + } + + CFRelease(streamError); + } + + THIS->m_delegate->streamErrorOccurred(reportedNetworkError); + if (reportedNetworkError) { + CFRelease(reportedNetworkError); + } + } + break; + } + } +} + +} // namespace astreamer diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.h new file mode 100644 index 0000000..344ae36 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/http_stream.h @@ -0,0 +1,98 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_HTTP_STREAM_H +#define ASTREAMER_HTTP_STREAM_H + +#import +#import +#import +#import "input_stream.h" +#import "id3_parser.h" + +namespace astreamer { + +class HTTP_Stream : public Input_Stream { +private: + + HTTP_Stream(const HTTP_Stream&); + HTTP_Stream& operator=(const HTTP_Stream&); + + static CFStringRef httpRequestMethod; + static CFStringRef httpUserAgentHeader; + static CFStringRef httpRangeHeader; + static CFStringRef icyMetaDataHeader; + static CFStringRef icyMetaDataValue; + + CFURLRef m_url; + CFReadStreamRef m_readStream; + bool m_scheduledInRunLoop; + bool m_readPending; + Input_Stream_Position m_position; + + /* HTTP headers */ + bool m_httpHeadersParsed; + CFStringRef m_contentType; + size_t m_contentLength; + UInt64 m_bytesRead; + + /* ICY protocol */ + bool m_icyStream; + bool m_icyHeaderCR; + bool m_icyHeadersRead; + bool m_icyHeadersParsed; + + CFStringRef m_icyName; + + std::vector m_icyHeaderLines; + size_t m_icyMetaDataInterval; + size_t m_dataByteReadCount; + size_t m_metaDataBytesRemaining; + + std::vector m_icyMetaData; + + /* Read buffers */ + UInt8 *m_httpReadBuffer; + UInt8 *m_icyReadBuffer; + + ID3_Parser *m_id3Parser; + + CFReadStreamRef createReadStream(CFURLRef url); + void parseHttpHeadersIfNeeded(const UInt8 *buf, const CFIndex bufSize); + void parseICYStream(const UInt8 *buf, const CFIndex bufSize); + CFStringRef createMetaDataStringWithMostReasonableEncoding(const UInt8 *bytes, const CFIndex numBytes); + + static void readCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo); + +public: + HTTP_Stream(); + virtual ~HTTP_Stream(); + + Input_Stream_Position position(); + + CFStringRef contentType(); + size_t contentLength(); + + bool open(); + bool open(const Input_Stream_Position& position); + void close(); + + void setScheduledInRunLoop(bool scheduledInRunLoop); + + void setUrl(CFURLRef url); + + static bool canHandleUrl(CFURLRef url); + + /* ID3_Parser_Delegate */ + void id3metaDataAvailable(std::map metaData); + void id3tagSizeAvailable(UInt32 tagSize); +}; + +} // namespace astreamer + +#endif // ASTREAMER_HTTP_STREAM_H diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.cpp new file mode 100644 index 0000000..2bc1441 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.cpp @@ -0,0 +1,536 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "id3_parser.h" + +#include + +//#define ID3_DEBUG 1 + +#if !defined ( ID3_DEBUG) +#define ID3_TRACE(...) do {} while (0) +#else +#define ID3_TRACE(...) printf(__VA_ARGS__) +#endif + +namespace astreamer { + +// Code from: +// http://www.opensource.apple.com/source/libsecurity_manifest/libsecurity_manifest-29384/lib/SecureDownloadInternal.c + +// Returns a CFString containing the base64 representation of the data. +// boolean argument for whether to line wrap at 64 columns or not. +CFStringRef createBase64EncodedString(const UInt8* ptr, size_t len, int wrap) { + const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/="; + + // base64 encoded data uses 4 ASCII characters to represent 3 octets. + // There can be up to two == at the end of the base64 data for padding. + // If we are line wrapping then we need space for one newline character + // every 64 characters of output. + // Rounded 4/3 up to 2 to avoid floating point math. + + //CFIndex max_len = (2*len) + 2; + //if (wrap) len = len + ((2*len) / 64) + 1; + + CFMutableStringRef string = CFStringCreateMutable(NULL, 0); + if (!string) return NULL; + + /* + http://www.faqs.org/rfcs/rfc3548.html + +--first octet--+-second octet--+--third octet--+ + |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + +-----------+---+-------+-------+---+-----------+ + |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| + +--1.index--+--2.index--+--3.index--+--4.index--+ + */ + int i = 0; // octet offset into input data + int column = 0; // output column number (used for line wrapping) + for (;;) { + UniChar c[16]; // buffer of characters to add to output + int j = 0; // offset to place next character in buffer + int index; // index into output alphabet + +#define ADDCHAR(_X_) do { c[j++] = _X_; if (wrap && (++column == 64)) { column = 0; c[j++] = '\n'; } } while (0); + + // 1.index + index = (ptr[i] >> 2) & 0x3F; + ADDCHAR(alphabet[index]); + + // 2.index + index = (ptr[i] << 4) & 0x30; + if ((i+1) < len) { + index = index | ((ptr[i+1] >> 4) & 0x0F); + ADDCHAR(alphabet[index]); + } else { // end of input, pad as necessary + ADDCHAR(alphabet[index]); + ADDCHAR('='); + ADDCHAR('='); + } + + // 3.index + if ((i+1) < len) { + index = (ptr[i+1] << 2) & 0x3C; + if ((i+2) < len) { + index = index | ((ptr[i+2] >> 6) & 0x03); + ADDCHAR(alphabet[index]); + } else { // end of input, pad as necessary + ADDCHAR(alphabet[index]); + ADDCHAR('='); + } + } + + // 4.index + if ((i+2) < len) { + index = (ptr[i+2]) & 0x3F; + ADDCHAR(alphabet[index]); + } + + CFStringAppendCharacters(string, c, j); + i += 3; // we processed 3 bytes of input + if (i >= len) { + // end of data, append newline if we haven't already + if (wrap && c[j-1] != '\n') { + c[0] = '\n'; + CFStringAppendCharacters(string, c, 1); + } + break; + } + } + return string; +} + +enum ID3_Parser_State { + ID3_Parser_State_Initial = 0, + ID3_Parser_State_Parse_Frames, + ID3_Parser_State_Tag_Parsed, + ID3_Parser_State_Not_Valid_Tag +}; + +/* + * ======================================= + * Private class + * ======================================= + */ + +class ID3_Parser_Private { +public: + ID3_Parser_Private(); + ~ID3_Parser_Private(); + + bool wantData(); + void feedData(UInt8 *data, UInt32 numBytes); + void setState(ID3_Parser_State state); + void reset(); + + CFStringRef parseContent(UInt32 framesize, UInt32 pos, CFStringEncoding encoding, bool byteOrderMark); + + ID3_Parser *m_parser; + ID3_Parser_State m_state; + UInt32 m_bytesReceived; + UInt32 m_tagSize; + UInt8 m_majorVersion; + bool m_hasFooter; + bool m_usesUnsynchronisation; + bool m_usesExtendedHeader; + CFStringRef m_title; + CFStringRef m_performer; + CFStringRef m_coverArt; + + std::vector m_tagData; +}; + +/* + * ======================================= + * Private class implementation + * ======================================= + */ + +ID3_Parser_Private::ID3_Parser_Private() : + m_parser(0), + m_state(ID3_Parser_State_Initial), + m_bytesReceived(0), + m_tagSize(0), + m_majorVersion(0), + m_hasFooter(false), + m_usesUnsynchronisation(false), + m_usesExtendedHeader(false), + m_title(NULL), + m_performer(NULL), + m_coverArt(NULL) +{ +} + +ID3_Parser_Private::~ID3_Parser_Private() +{ + if (m_performer) { + CFRelease(m_performer); + m_performer = NULL; + } + if (m_title) { + CFRelease(m_title); + m_title = NULL; + } + if (m_coverArt) { + CFRelease(m_coverArt); + m_coverArt = NULL; + } +} + +bool ID3_Parser_Private::wantData() +{ + if (m_state == ID3_Parser_State_Tag_Parsed) { + return false; + } + if (m_state == ID3_Parser_State_Not_Valid_Tag) { + return false; + } + + return true; +} + +void ID3_Parser_Private::feedData(UInt8 *data, UInt32 numBytes) +{ + if (!wantData()) { + return; + } + + m_bytesReceived += numBytes; + + ID3_TRACE("received %i bytes, total bytes %i\n", numBytes, m_bytesReceived); + + for (CFIndex i=0; i < numBytes; i++) { + m_tagData.push_back(data[i]); + } + + bool enoughBytesToParse = true; + + while (enoughBytesToParse) { + switch (m_state) { + case ID3_Parser_State_Initial: { + // Do we have enough bytes to determine if this is an ID3 tag or not? + if (m_bytesReceived <= 9) { + enoughBytesToParse = false; + break; + } + + if (!(m_tagData[0] == 'I' && + m_tagData[1] == 'D' && + m_tagData[2] == '3')) { + ID3_TRACE("Not an ID3 tag, bailing out\n"); + + // Does not begin with the tag header; not an ID3 tag + setState(ID3_Parser_State_Not_Valid_Tag); + enoughBytesToParse = false; + break; + } + + m_majorVersion = m_tagData[3]; + // Currently support only id3v2.2 and 2.3 + if (m_majorVersion != 2 && m_majorVersion != 3) { + ID3_TRACE("ID3v2.%i not supported by the parser\n", m_majorVersion); + + setState(ID3_Parser_State_Not_Valid_Tag); + enoughBytesToParse = false; + break; + } + + // Ignore the revision + + // Parse the flags + + if ((m_tagData[5] & 0x80) != 0) { + m_usesUnsynchronisation = true; + } else if ((m_tagData[5] & 0x40) != 0 && m_majorVersion >= 3) { + m_usesExtendedHeader = true; + } else if ((m_tagData[5] & 0x10) != 0 && m_majorVersion >= 3) { + m_hasFooter = true; + } + + m_tagSize = ((m_tagData[6] & 0x7F) << 21) | ((m_tagData[7] & 0x7F) << 14) | + ((m_tagData[8] & 0x7F) << 7) | (m_tagData[9] & 0x7F); + + if (m_tagSize > 0) { + if (m_hasFooter) { + m_tagSize += 10; + } + m_tagSize += 10; + + ID3_TRACE("tag size: %i\n", m_tagSize); + + if (m_parser->m_delegate) { + m_parser->m_delegate->id3tagSizeAvailable(m_tagSize); + } + + setState(ID3_Parser_State_Parse_Frames); + break; + } + + setState(ID3_Parser_State_Not_Valid_Tag); + enoughBytesToParse = false; + break; + } + + case ID3_Parser_State_Parse_Frames: { + // Do we have enough data to parse the frames? + if (m_tagData.size() < m_tagSize) { + ID3_TRACE("Not enough data received for parsing, have %lu bytes, need %i bytes\n", + m_tagData.size(), + m_tagSize); + enoughBytesToParse = false; + break; + } + + UInt32 pos = 10; + + // Do we have an extended header? If we do, skip it + if (m_usesExtendedHeader) { + UInt32 extendedHeaderSize = ((m_tagData[pos] << 21) | + (m_tagData[pos+1] << 14) | + (m_tagData[pos+2] << 7) | + m_tagData[pos+3]); + + if (pos + extendedHeaderSize >= m_tagSize) { + setState(ID3_Parser_State_Not_Valid_Tag); + enoughBytesToParse = false; + break; + } + + ID3_TRACE("Skipping extended header, size %i\n", extendedHeaderSize); + + pos += extendedHeaderSize; + } + + while (pos < m_tagSize) { + char frameName[5]; + frameName[0] = m_tagData[pos]; + frameName[1] = m_tagData[pos+1]; + frameName[2] = m_tagData[pos+2]; + + if (m_majorVersion >= 3) { + frameName[3] = m_tagData[pos+3]; + } else { + frameName[3] = 0; + } + frameName[4] = 0; + + UInt32 framesize = 0; + + if (m_majorVersion >= 3) { + pos += 4; + + framesize = ((m_tagData[pos] << 21) | + (m_tagData[pos+1] << 14) | + (m_tagData[pos+2] << 7) | + m_tagData[pos+3]); + } else { + pos += 3; + + framesize = ((m_tagData[pos] << 16) | + (m_tagData[pos+1] << 8) | + m_tagData[pos+2]); + } + + if (framesize == 0) { + setState(ID3_Parser_State_Not_Valid_Tag); + enoughBytesToParse = false; + // Break from the loop and then out of the case context + goto ParseFramesExit; + } + + if (m_majorVersion >= 3) { + pos += 6; + } else { + pos += 3; + } + + CFStringEncoding encoding; + bool byteOrderMark = false; + + if (m_tagData[pos] == 3) { + encoding = kCFStringEncodingUTF8; + } else if (m_tagData[pos] == 2) { + encoding = kCFStringEncodingUTF16BE; + } else if (m_tagData[pos] == 1) { + encoding = kCFStringEncodingUTF16; + byteOrderMark = true; + } else { + // ISO-8859-1 is the default encoding + encoding = kCFStringEncodingISOLatin1; + } + + if (!strcmp(frameName, "TIT2") || !strcmp(frameName, "TT2")) { + if (m_title) { + CFRelease(m_title); + } + m_title = parseContent(framesize, pos + 1, encoding, byteOrderMark); + + ID3_TRACE("ID3 title parsed: '%s'\n", CFStringGetCStringPtr(m_title, CFStringGetSystemEncoding())); + } else if (!strcmp(frameName, "TPE1") || !strcmp(frameName, "TP1")) { + if (m_performer) { + CFRelease(m_performer); + } + m_performer = parseContent(framesize, pos + 1, encoding, byteOrderMark); + + ID3_TRACE("ID3 performer parsed: '%s'\n", CFStringGetCStringPtr(m_performer, CFStringGetSystemEncoding())); + } else if (!strcmp(frameName, "APIC")) { + char imageType[65] = {0}; + + size_t dataPos = pos+1; + + for (int i=0; m_tagData[dataPos]; i++,dataPos++) { + imageType[i] = m_tagData[dataPos]; + } + dataPos++; + + if (!strcmp(imageType, "image/jpeg") || + !strcmp(imageType, "image/png")) { + + ID3_TRACE("Image type %s, parsing, dataPos %zu\n", imageType, dataPos); + + // Skip the image description + while (!m_tagData[++dataPos]); + + const size_t coverArtSize = framesize - ((dataPos - pos) + 5); + + UInt8 *bytes = new UInt8[coverArtSize]; + + for (int i=0; i < coverArtSize; i++) { + bytes[i] = m_tagData[dataPos+i]; + } + + if (m_coverArt) { + CFRelease(m_coverArt); + } + m_coverArt = createBase64EncodedString(bytes, coverArtSize, 0); + + delete [] bytes; + } else { + ID3_TRACE("%s is an unknown type for image data, skipping\n", imageType); + } + } else { + // Unknown/unhandled frame + ID3_TRACE("Unknown/unhandled frame: %s, size %i\n", frameName, framesize); + } + + pos += framesize; + } + + // Push out the metadata + if (m_parser->m_delegate) { + std::map metadataMap; + + if (m_performer && CFStringGetLength(m_performer) > 0) { + metadataMap[CFSTR("MPMediaItemPropertyArtist")] = + CFStringCreateCopy(kCFAllocatorDefault, m_performer); + } + + if (m_title && CFStringGetLength(m_title) > 0) { + metadataMap[CFSTR("MPMediaItemPropertyTitle")] = + CFStringCreateCopy(kCFAllocatorDefault, m_title); + } + + if (m_coverArt && CFStringGetLength(m_coverArt) > 0) { + metadataMap[CFSTR("CoverArt")] = + CFStringCreateCopy(kCFAllocatorDefault, m_coverArt); + } + + m_parser->m_delegate->id3metaDataAvailable(metadataMap); + } + + setState(ID3_Parser_State_Tag_Parsed); + enoughBytesToParse = false; +ParseFramesExit: + break; + } + + default: + enoughBytesToParse = false; + break; + } + } +} + +void ID3_Parser_Private::setState(astreamer::ID3_Parser_State state) +{ + m_state = state; +} + +void ID3_Parser_Private::reset() +{ + m_state = ID3_Parser_State_Initial; + m_bytesReceived = 0; + m_tagSize = 0; + m_majorVersion = 0; + m_hasFooter = false; + m_usesUnsynchronisation = false; + m_usesExtendedHeader = false; + + if (m_title) { + CFRelease(m_title); + m_title = NULL; + } + if (m_performer) { + CFRelease(m_performer); + m_performer = NULL; + } + if (m_coverArt) { + CFRelease(m_coverArt); + m_coverArt = NULL; + } + + m_tagData.clear(); +} + +CFStringRef ID3_Parser_Private::parseContent(UInt32 framesize, UInt32 pos, CFStringEncoding encoding, bool byteOrderMark) +{ + CFStringRef content = CFStringCreateWithBytes(kCFAllocatorDefault, + &m_tagData[pos], + framesize - 1, + encoding, + byteOrderMark); + + return content; +} + +/* + * ======================================= + * ID3_Parser implementation + * ======================================= + */ + +ID3_Parser::ID3_Parser() : + m_delegate(0), + m_private(new ID3_Parser_Private()) +{ + m_private->m_parser = this; +} + +ID3_Parser::~ID3_Parser() +{ + delete m_private; + m_private = 0; +} + +void ID3_Parser::reset() +{ + m_private->reset(); +} + +bool ID3_Parser::wantData() +{ + return m_private->wantData(); +} + +void ID3_Parser::feedData(UInt8 *data, UInt32 numBytes) +{ + m_private->feedData(data, numBytes); +} + +} diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.h new file mode 100644 index 0000000..3eae1d7 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/id3_parser.h @@ -0,0 +1,44 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_ID3_PARSER_H +#define ASTREAMER_ID3_PARSER_H + +#include + +#import + +namespace astreamer { + +class ID3_Parser_Delegate; +class ID3_Parser_Private; + +class ID3_Parser { +public: + ID3_Parser(); + ~ID3_Parser(); + + void reset(); + bool wantData(); + void feedData(UInt8 *data, UInt32 numBytes); + + ID3_Parser_Delegate *m_delegate; + +private: + ID3_Parser_Private *m_private; +}; + +class ID3_Parser_Delegate { +public: + virtual void id3metaDataAvailable(std::map metaData) = 0; + virtual void id3tagSizeAvailable(UInt32 tagSize) = 0; +}; + +} // namespace astreamer + +#endif // ASTREAMER_ID3_PARSER_H diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.cpp new file mode 100644 index 0000000..15aa0f3 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.cpp @@ -0,0 +1,21 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "input_stream.h" + +namespace astreamer { + +Input_Stream::Input_Stream() : m_delegate(0) +{ +} + +Input_Stream::~Input_Stream() +{ +} + +} diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.h new file mode 100644 index 0000000..bd1ddf3 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/input_stream.h @@ -0,0 +1,56 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_INPUT_STREAM_H +#define ASTREAMER_INPUT_STREAM_H + +#import "id3_parser.h" + +namespace astreamer { + +class Input_Stream_Delegate; + +struct Input_Stream_Position { + UInt64 start; + UInt64 end; +}; + +class Input_Stream : public ID3_Parser_Delegate { +public: + Input_Stream(); + virtual ~Input_Stream(); + + Input_Stream_Delegate* m_delegate; + + virtual Input_Stream_Position position() = 0; + + virtual CFStringRef contentType() = 0; + virtual size_t contentLength() = 0; + + virtual bool open() = 0; + virtual bool open(const Input_Stream_Position& position) = 0; + virtual void close() = 0; + + virtual void setScheduledInRunLoop(bool scheduledInRunLoop) = 0; + + virtual void setUrl(CFURLRef url) = 0; +}; + +class Input_Stream_Delegate { +public: + virtual void streamIsReadyRead() = 0; + virtual void streamHasBytesAvailable(UInt8 *data, UInt32 numBytes) = 0; + virtual void streamEndEncountered() = 0; + virtual void streamErrorOccurred(CFStringRef errorDesc) = 0; + virtual void streamMetaDataAvailable(std::map metaData) = 0; + virtual void streamMetaDataByteSizeAvailable(UInt32 sizeInBytes) = 0; +}; + +} // namespace astreamer + +#endif // ASTREAMER_INPUT_STREAM_H diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.cpp b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.cpp new file mode 100644 index 0000000..0f410b3 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.cpp @@ -0,0 +1,34 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#include "stream_configuration.h" + +namespace astreamer { + +Stream_Configuration::Stream_Configuration() : + userAgent(NULL), + cacheDirectory(NULL), + predefinedHttpHeaderValues(NULL) +{ +} + +Stream_Configuration::~Stream_Configuration() +{ + if (userAgent) { + CFRelease(userAgent); + userAgent = NULL; + } +} + +Stream_Configuration* Stream_Configuration::configuration() +{ + static Stream_Configuration config; + return &config; +} + +} diff --git a/Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.h b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.h new file mode 100644 index 0000000..64596f7 --- /dev/null +++ b/Pods/FreeStreamer/FreeStreamer/FreeStreamer/stream_configuration.h @@ -0,0 +1,55 @@ +/* + * This file is part of the FreeStreamer project, + * (C)Copyright 2011-2018 Matias Muhonen 穆马帝 + * See the file ''LICENSE'' for using the code. + * + * https://github.com/muhku/FreeStreamer + */ + +#ifndef ASTREAMER_STREAM_CONFIGURATION_H +#define ASTREAMER_STREAM_CONFIGURATION_H + +#import + +namespace astreamer { + +struct Stream_Configuration { + unsigned bufferCount; + unsigned bufferSize; + unsigned maxPacketDescs; + unsigned httpConnectionBufferSize; + double outputSampleRate; + long outputNumChannels; + int bounceInterval; + int maxBounceCount; + int startupWatchdogPeriod; + int maxPrebufferedByteCount; + bool usePrebufferSizeCalculationInSeconds; + bool usePrebufferSizeCalculationInPackets; + int requiredInitialPrebufferedByteCountForContinuousStream; + int requiredInitialPrebufferedByteCountForNonContinuousStream; + int requiredPrebufferSizeInSeconds; + int requiredInitialPrebufferedPacketCount; + CFStringRef userAgent; + CFStringRef cacheDirectory; + CFDictionaryRef predefinedHttpHeaderValues; + bool cacheEnabled; + bool seekingFromCacheEnabled; + bool automaticAudioSessionHandlingEnabled; + bool enableTimeAndPitchConversion; + bool requireStrictContentTypeChecking; + int maxDiskCacheSize; + + static Stream_Configuration *configuration(); + +private: + Stream_Configuration(); + ~Stream_Configuration(); + + Stream_Configuration(const Stream_Configuration&); + Stream_Configuration& operator=(const Stream_Configuration&); +}; + +} // namespace astreamer + +#endif // ASTREAMER_STREAM_CONFIGURATION_H diff --git a/Pods/FreeStreamer/LICENSE.txt b/Pods/FreeStreamer/LICENSE.txt new file mode 100644 index 0000000..ed0e0b0 --- /dev/null +++ b/Pods/FreeStreamer/LICENSE.txt @@ -0,0 +1,53 @@ +Copyright (c) 2011-2018 Matias Muhonen 穆马帝 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +The FreeStreamer framework bundles Reachability which is licensed under the following +license: + +Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Pods/FreeStreamer/README.markdown b/Pods/FreeStreamer/README.markdown new file mode 100644 index 0000000..59cf775 --- /dev/null +++ b/Pods/FreeStreamer/README.markdown @@ -0,0 +1,41 @@ +FreeStreamer +==================== + +A streaming audio player for iOS and OS X. + +Features +==================== + +- **CPU-friendly** design (uses 1% of CPU on average when streaming) +- **Multiple protocols supported**: ShoutCast, standard HTTP, local files +- **Prepared for tough network conditions**: adjustable buffer sizes, stream pre-buffering and restart on failures +- **Metadata support**: ShoutCast metadata, IDv2 tags +- **Local disk caching**: user only needs to stream a file once and after that it can be played from a local cache +- **Preloading**: playback can start immediately without needing to wait for buffering +- **Record**: support recording the stream contents to a file +- **Access the PCM audio samples**: as an example, a visualizer is included + +Documentation +==================== + +See the [FAQ](https://github.com/muhku/FreeStreamer/wiki/FreeStreamer-FAQ) (Frequently Asked Questions) in the wiki. We also have an [API documentation](http://muhku.github.io/api/) available. The [usage instructions](https://github.com/muhku/FreeStreamer/wiki/Using-the-player-in-your-own-project) are also covered in the wiki. + +Is somebody using this in real life? +==================== + +The short answer is yes! Check out our [website](http://muhku.github.io/) for the reference applications. + +Reporting bugs and contributing +==================== + +For code contributions and other questions, it is preferrable to create a Github pull request. I don't have time for private email support, so usually the best way to get help is to interact with Github issues. + +License +==================== + +See [LICENSE.txt](https://github.com/muhku/FreeStreamer/blob/master/LICENSE.txt) for the license. + +Donations +==================== + +It is possible to use [PayPal](http://muhku.github.io/donate.html) for donations. diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock index 3302729..d85e74c 100644 --- a/Pods/Manifest.lock +++ b/Pods/Manifest.lock @@ -1,18 +1,23 @@ PODS: - Alamofire (5.9.1) + - FreeStreamer (4.0.0): + - Reachability (~> 3.0) - IQKeyboardManagerSwift (6.5.16) - JXPagingView/Paging (2.1.3) - JXSegmentedView (1.3.3) - Kingfisher (7.11.0) - MJRefresh (3.7.9) + - Reachability (3.7.6) - SnapKit (5.7.1) - SVProgressHUD (2.3.1): - SVProgressHUD/Core (= 2.3.1) - SVProgressHUD/Core (2.3.1) - SwiftDate (6.3.1) + - Tiercel (3.2.5) DEPENDENCIES: - Alamofire + - FreeStreamer - IQKeyboardManagerSwift - JXPagingView/Paging - JXSegmentedView @@ -21,30 +26,37 @@ DEPENDENCIES: - SnapKit - SVProgressHUD - SwiftDate + - Tiercel SPEC REPOS: trunk: - Alamofire + - FreeStreamer - IQKeyboardManagerSwift - JXPagingView - JXSegmentedView - Kingfisher - MJRefresh + - Reachability - SnapKit - SVProgressHUD - SwiftDate + - Tiercel SPEC CHECKSUMS: Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c + FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea IQKeyboardManagerSwift: 12d89768845bb77b55cc092ecc2b1f9370f06b76 JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e JXSegmentedView: 651b60fcf705258ba9395edd53876dbd2853fb68 Kingfisher: b9c985d864d43515f404f1ef4a8ce7d802ace3ac MJRefresh: ff9e531227924c84ce459338414550a05d2aea78 + Reachability: fd0ecd23705e2599e4cceeb943222ae02296cbc6 SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22 SwiftDate: 72d28954e8e1c6c1c0f917ccc8005e4f83c7d4b2 + Tiercel: c0a73f876a72800333b15f4e7e48791f4ad21e90 -PODFILE CHECKSUM: ba88795291c32ea83d380e5384537ca7f5568cd7 +PODFILE CHECKSUM: 3804949e23587f6d341ef21aa5e0b1c55a818968 COCOAPODS: 1.15.2 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index 0080b76..53dd796 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -7,902 +7,1108 @@ objects = { /* Begin PBXBuildFile section */ - 018E14B621F0A041F5109C9B4028DAA7 /* JXSegmentedIndicatorGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135384F59FC764376A1E1806D1B43835 /* JXSegmentedIndicatorGradientView.swift */; }; - 02298A4639A27A1808AA3188B7A566E9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E90BED91E412505AF7055D21B806502E /* PrivacyInfo.xcprivacy */; }; - 0335018FAC1AD7BC453F8F9A68CDABC4 /* SVProgressAnimatedView.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AAB28B6A1A5AA4CC4A0EEB258D9DC57 /* SVProgressAnimatedView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0361F2B5CB823772710AB366D00D3F0E /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF651FECE01069367CDE0054F13249F /* Formatter+Protocols.swift */; }; - 0493E66044259CA8F47711626563E29C /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 418EE8CAF2C7190F191FC7554EAE20D3 /* RelativeFormatterLanguage.swift */; }; - 05B82D21BB4500108A38518E64D25A6E /* CPListItem+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCF0C8D768EFB2E7C6A48EB2256867E /* CPListItem+Kingfisher.swift */; }; - 07F83DE63FB5CC8015F48F7B9B800B6F /* IQPreviousNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF0E1D3C22D93C0C7B2C51A16E17913 /* IQPreviousNextView.swift */; }; - 07FD274BC01F578C7B37989F79350C1E /* IQToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AA5BD68BC8AE62D30B207A9A089DBE2 /* IQToolbar.swift */; }; - 080B0A6E64DD22DBD5BF269AA6A556FB /* KFAnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D15BEF86A67CAF30B602BB5A260F77B /* KFAnimatedImage.swift */; }; - 081815B67871C182C0D337274DEDCE69 /* MJRefreshBackStateFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = E770B62A86FAE28308923E8266E15451 /* MJRefreshBackStateFooter.m */; }; - 08593ACC3F8955B57D3865FBE5940230 /* JXPagingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C866B997568C25289AC4481D960976 /* JXPagingView.swift */; }; - 0874E6176184E3A1C3E8AB158AE5E98B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 828F1C480D9959D94E2868CE1084677F /* CoreGraphics.framework */; }; - 09871A1E8CE997DD91E7ACE2A0A273BF /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E9A15EC42E241A39E5CABDB5E0D428 /* Zones.swift */; }; - 0A5C290276F7453D9471E25BEA655842 /* ConstraintLayoutGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1384016DEBC0AD27C484B338B4FBEDB5 /* ConstraintLayoutGuide.swift */; }; - 0AD7A0DA8FF5880E3D8599062129AB12 /* ConstraintMakerEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F088EAD83DD6725075FBD8403594C429 /* ConstraintMakerEditable.swift */; }; - 0CC41A7F3CDAAF90C1825DCF4FB71B9B /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0917B962F89415FDAAA876040A72E83 /* DateRepresentable.swift */; }; - 0D665B6767B345D8C70D7E029A2A48D8 /* ConstraintViewDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CC5E0E6473324DB3F517B54470CBE6C /* ConstraintViewDSL.swift */; }; - 0DA9A9D2F8960818017E26DA480FA143 /* ConstraintLayoutGuideDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 482206CB35A8E05C272FA37C0A8BF762 /* ConstraintLayoutGuideDSL.swift */; }; - 0F1D68554CA1AC595168E8FB4E1A6E63 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28BCEACC594F9EA2E10117D26B3DA759 /* CachedResponseHandler.swift */; }; - 0FBCA5C01FA487754C6D0B1690FC9CDA /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F796615F5FF6E91238484B197D6CF227 /* PrivacyInfo.xcprivacy */; }; - 1093AAD9CB7394BDBDD0E287103515F6 /* MJRefreshNormalTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = D18ABD1C495B7F72F0284AAC613A93F2 /* MJRefreshNormalTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1102439674F0EB249240589D2607B9EF /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF651BDF14D5A324318BC3F8C317F57A /* Result.swift */; }; - 11F2B1DF419EC68AD10D2A1D1176B082 /* SwiftDate-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 207F4383A0CAE6E946319D62026A82F0 /* SwiftDate-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 12EC6890A8A59536867A18E8F83C8BB7 /* JXPagingView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3EFEBDB3A2CB8EFD1E1BB0C882FC6500 /* JXPagingView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1336E9B0D82E5DE4E4DA4DAD898489BC /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C51E9357636F513F9828759A831069 /* DateInRegion+Create.swift */; }; - 13CC0CFB1C56F23BBC872801F983C1A3 /* ConstraintLayoutSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AFFBD2618F9F069B2DFFC4829E58831 /* ConstraintLayoutSupport.swift */; }; - 15CC896E2126D6A47D03C01964D74776 /* JXSegmentedIndicatorGradientLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC9CFB040D783BBBD3E787C142BFF2A /* JXSegmentedIndicatorGradientLineView.swift */; }; - 15F68E09ADCDB843B938C490059485BD /* ConstraintMakerRelatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABF6319E31E3E401B63195B3374007D /* ConstraintMakerRelatable+Extensions.swift */; }; - 162B358A9CC48A414D6744BAF8562D93 /* JXSegmentedTitleDynamicConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225AEBA0EC32A3D5A52ECA46DBBED7F3 /* JXSegmentedTitleDynamicConfiguration.swift */; }; - 16E5F9C7FE2C499D21F3E7AF1FFC2FA7 /* IQKeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E886364CE3D1DE31930B53C67EFBEB /* IQKeyboardManager.swift */; }; - 170E0C2556FD00466C155473B428F792 /* MJRefreshBackFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = D5A9B7775663040A8BD50618EDF84298 /* MJRefreshBackFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 172D6F4BDBFD921ABF3F4936747C7B5F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - 18B137C1708A9B9DC45BBB1ECF1CE4A8 /* MJRefreshHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 612886EAB1AE5139D8545245115B7EC8 /* MJRefreshHeader.m */; }; - 19C5CA44D4D4D5B711D33A73525D453F /* IQUIView+IQKeyboardToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC81E237B242F0601E0FC35F98708DC4 /* IQUIView+IQKeyboardToolbar.swift */; }; - 19FE9BC7F25FF5D48B6B2B3B520D4D47 /* SVProgressHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C589304DE45B301793ACB48B3D74C25 /* SVProgressHUD.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1A39B80A5083C84CF22A0D9FBB27761E /* UICollectionViewLayout+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DA3BDF290E39DAB4D1F114C808EB725 /* UICollectionViewLayout+MJRefresh.m */; }; - 1A56A2E0BE0367DDB6BE95E537D43314 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E619DA6F3D601658833035BDA0E167C /* Commons.swift */; }; - 1AEABE21257723E2AEE836117F3A8B12 /* MJRefreshFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A99AB9AED98C1B656C310507C966514 /* MJRefreshFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1B4B180E84924CF99663817A2AFFAA92 /* IQKeyboardManager+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17C586A0C27A8F810C609FC78419447 /* IQKeyboardManager+Debug.swift */; }; - 1E7E37C51375285EA22294CD2ADB3159 /* Debugging.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88C5C3FE92321767662E35C421964B3 /* Debugging.swift */; }; - 1FF54BA1EA87ACD81339325012E2537B /* JXSegmentedTitleGradientCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2820DA7141C1E36BCF6D4D5D96802EAF /* JXSegmentedTitleGradientCell.swift */; }; - 218C0927BB7605141B611529DD6F65B4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - 222B9B50C37D319BBACE8E15B8A40348 /* JXSegmentedNumberDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6A17326897FCD562597FF862EFB28 /* JXSegmentedNumberDataSource.swift */; }; - 22FAFA41450EC40132CF4B0EEE7E6788 /* ParameterEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC28BE75055A99E1BD5B4B8D07E3C1B /* ParameterEncoder.swift */; }; - 23277D4B1F4E8335DDCAB69A2AD75C9F /* JXSegmentedTitleOrImageDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCD84A7AB4756552C1E55E266165ACA /* JXSegmentedTitleOrImageDataSource.swift */; }; - 23B1B9EA25E2C943FCE5EC6D4EE19D6A /* MJRefreshGifHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 70D291797444B210FC11B05604380747 /* MJRefreshGifHeader.m */; }; - 23EE2A5A9ADF9A6B9A3515CA85E382ED /* UIView+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 810BB8FC42B516FF79349CC6C8C13E4C /* UIView+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 244119912087C6ECC078CB0E740833EB /* UIScrollView+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = D38764AB52ACD239BAD933070283AA4F /* UIScrollView+MJRefresh.m */; }; - 2480B116D9A738E79212C0E0DD41872D /* GIFAnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D394AD7BFA59E7271EC623CF17FBDD30 /* GIFAnimatedImage.swift */; }; - 26541C9BEC53081C7AD9BB9819D71B41 /* JXSegmentedNumberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFFBC6D1E5E46588B6AAB4F0711B9D5E /* JXSegmentedNumberCell.swift */; }; - 274E0D56AEB27C391FC58EBED912F19A /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D613FC602AD1B3823E553095A17BB58 /* Image.swift */; }; - 279E3C4211663A097FFE575249B19BC3 /* SVProgressAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = BD1BE557E2AE4A84F2515EB14ABAF97B /* SVProgressAnimatedView.m */; }; - 2927AE446998C11370D49E4A2CFD17AC /* MJRefreshNormalHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F1162C00BCBB4CECFD5CA05ED5667F3 /* MJRefreshNormalHeader.m */; }; - 2AE3EFDE302E34CF498E6A693ACC9D06 /* ImageDataProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B6DF66CC0D9538F8F45AB7F7F82FB3 /* ImageDataProcessor.swift */; }; - 2B1AAA73D9231A2A7BA89DD1FF01C273 /* JXSegmentedTitleImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55914F40E7C5BD463245E2016A8906BC /* JXSegmentedTitleImageCell.swift */; }; - 2B230B24827053BA3E9DA0C78A796BC2 /* ResponseSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88F8FEC13A3A1DB04F7186FA15AE1CB /* ResponseSerialization.swift */; }; - 2B42D035AFF52D62722161A7772C6C08 /* HTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5699EE5AE55BF8C2FE30604FAED64A7C /* HTTPHeaders.swift */; }; - 2C1014EEE6B2404C98A9F7AA534B534C /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = C50C61572CB504F3A1B27F44B20F385F /* PrivacyInfo.xcprivacy */; }; - 2C4C08BB733A2101D945E8C37256F78F /* UploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B4EE5D13ED112C5C84DB3A6ADE8E7E0 /* UploadRequest.swift */; }; - 2C94C305900E95BDB9296A7EFCF3C940 /* MJRefreshConst.h in Headers */ = {isa = PBXBuildFile; fileRef = 03538AE63A9932FE6DD18C82468E190F /* MJRefreshConst.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2DD965DE434BE3B5A0C6EC1FC47AE81D /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DF245C6F43F9612AD56BC7D2ADB123 /* String+Parser.swift */; }; - 2E572B53624BB2ED444BC9747A9F1781 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1E44B32910D0CD9054692D4F938398 /* ImageDownloader.swift */; }; - 2E84CD435150EDA1356EE23EAB3CE5E7 /* SVRadialGradientLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = F6D3AF216BBC3443539D5BAD7BE5DEDF /* SVRadialGradientLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3013EFF5D3DB5F626FB63C96A690082A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - 3031C92E9CB81AD378483755A4DA6F79 /* KingfisherManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DAC31F0F6FE66D2C8AF5489F272CEA /* KingfisherManager.swift */; }; - 3035E05E0216ED11647680F9C2127B01 /* MJRefresh.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 84FCB0C95F94873F8D87374CE1ACA147 /* MJRefresh.bundle */; }; - 30DBC87A501715FE2328B9FF011F1BB9 /* MJRefreshStateHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 82CD43FF64DC36B945840368DAC1706E /* MJRefreshStateHeader.m */; }; - 32AD6316A2024235B39F19FC97C0B6BF /* ImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C408D0CD682A652982659893260F5EA4 /* ImageDataProvider.swift */; }; - 3360B725B480679D59E44B812DD8D116 /* MJRefresh-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 82E922355D2129F96245BA8E4E5FFF67 /* MJRefresh-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 346855A32221981DE16BBB5C1E636BA4 /* MJRefreshAutoStateFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = BE8BB5A012C6C501F12A4876ABF49101 /* MJRefreshAutoStateFooter.m */; }; - 34DE07309F7BF7F614A480667602D079 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 832AF59B8B94B734BFA5598CF822D2E8 /* PrivacyInfo.xcprivacy */; }; - 360F2B06AC4C38F3994965AA69FF0AC7 /* Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83C7B6631D6A0479168A89D25B68A47C /* Kingfisher.swift */; }; - 36C78069A72BECAEB66B31FF794A09ED /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB8D6628308EE28230C57E4CE1063A31 /* Request.swift */; }; - 37289CBB73D25EA3424BF2ADFBB898AC /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513DEC141FDDFD1CDAA8ED56A66AF623 /* TimePeriodProtocol.swift */; }; - 373502D50A1B2266D86409E19EC46DDB /* IQTitleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D659C29A42861AC5A4EF3B9D26DC0B /* IQTitleBarButtonItem.swift */; }; - 37D302D5A81F84194396C4A7C871F1DA /* langs in Resources */ = {isa = PBXBuildFile; fileRef = B5819E5E132D7A725A5CF03163E7796D /* langs */; }; - 39EAB37ED329E6CE7C691B9EAF731446 /* JXSegmentedDotDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344A8E805D9E365E5FDF94223860238E /* JXSegmentedDotDataSource.swift */; }; - 3B573F7FAA3A12BA7BF2CC874A31DEAB /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48EF69AA55D5B91B47D8C1EB4BA4691 /* DateInRegion+Components.swift */; }; - 3B901DF82C6576019330391D0F470B31 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2A128F3FAE37601751167FDC47182A /* CFNetwork.framework */; }; - 3BC51136C1F65D6962C6727889AA942E /* String+MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7143F3A6986BF2AB2828E2DC37C9C09D /* String+MD5.swift */; }; - 3C425FEE22F5E6EE82500F6107EAE8F4 /* JXSegmentedIndicatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE60E3816FD1A788423E90F6E28D8D /* JXSegmentedIndicatorProtocol.swift */; }; - 3DAFEDCDC5094007E329D71FE84FF704 /* SnapKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = A933F95E4E12D348E925A9E3B27750CB /* SnapKit-dummy.m */; }; - 417EAFB7648F1C35B8F7ADC203A96315 /* JXSegmentedBaseItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A207FA0764909F90D1FCCDE191F924 /* JXSegmentedBaseItemModel.swift */; }; - 41A536E6CE1C85116A756B710B2C660B /* IQKeyboardManagerConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A89AE3F00FA615DB614031B7510C78F /* IQKeyboardManagerConstants.swift */; }; - 42B43A15ADA9F66F4E2AD66666943F26 /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B832310B3844FB85DE10E60B45F44A3 /* SwiftDate.swift */; }; - 4337931D8B8E3F3BA03C77C1B496BEAD /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D3E36D2866A136708CF5F602D268BD7 /* HTTPMethod.swift */; }; - 43ED71634C98518E6B0749001ED89C43 /* MJRefreshAutoNormalFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 6C5F4CF175A8588A2773B4AF89EA51EC /* MJRefreshAutoNormalFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 440C0830E1410552E2ED0A1647CC4209 /* JXPagingListRefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FA1854F15DFB5CA5EAC2BF88823143 /* JXPagingListRefreshView.swift */; }; - 4413F602F8263A6E3EE93517916F2EDF /* SwiftDate-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 43FF45102B67E91CC3D79928C2717E40 /* SwiftDate-dummy.m */; }; - 45131830DC22C22B4A21C4A54A147947 /* MJRefreshNormalTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 280637DA318DC5D912D98194C6844751 /* MJRefreshNormalTrailer.m */; }; - 45D1C5B966D0BE59EF67F8A839AED2F4 /* JXSegmentedViewTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB8A3AF649521A0ED645B8858FB4DDF /* JXSegmentedViewTool.swift */; }; - 47B7A22D43797E42FCDCD96028823BFF /* KFImageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F79F3D31F68D650253E6399BB7549D1 /* KFImageProtocol.swift */; }; - 4803DFE6BE7D9A926811385567288A8C /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44F0D93CFD9BB7C99C0FFCD686FF4E3 /* TimePeriodGroup.swift */; }; - 48E83D4BC4A5C3542CB0560A1E82A2F4 /* MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 1434E2DF42114AA07FFC65F0AF74B738 /* MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 498FFEC31A02476F4F719EF645AC1562 /* MJRefreshHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 8124055AC9E04813E53A3EE6EDD5E1DE /* MJRefreshHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4AADD25AAE104D5F72CDC039E82D9BB6 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A78C7902D8016F72AC215D2E28422F6 /* RedirectHandler.swift */; }; - 4B6EA5410A2C492D91A141426FD81588 /* WKInterfaceImage+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2893522A05B1339D4388FD46FF52C52A /* WKInterfaceImage+Kingfisher.swift */; }; - 4C1CC731DAB49793158DB4EA0982137C /* ImageBinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE5DD88C594D20A1F18F99828A3FEB64 /* ImageBinder.swift */; }; - 4D77099177C62FCD622E0BB73237386E /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 268D0497BC989AF2355C7E9712755278 /* CacheSerializer.swift */; }; - 4F2F7AFD7C593DAE210F77E7F8BC2403 /* JXSegmentedIndicatorTriangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B2FF8925A3677E79E08183E04491D1 /* JXSegmentedIndicatorTriangleView.swift */; }; + 007E4A9363B819089774B481510E7DFC /* DownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F5AE52581DB823B2E91EEB6FD22AF1 /* DownloadTask.swift */; }; + 018E14B621F0A041F5109C9B4028DAA7 /* JXSegmentedIndicatorGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06B0B50D662836095FC9FBDA523C32 /* JXSegmentedIndicatorGradientView.swift */; }; + 0335018FAC1AD7BC453F8F9A68CDABC4 /* SVProgressAnimatedView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4975EAB4702DC92577C2ADE5E71D1FBA /* SVProgressAnimatedView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0361F2B5CB823772710AB366D00D3F0E /* Formatter+Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306B8810C66C0963989D2FF077E79536 /* Formatter+Protocols.swift */; }; + 0493E66044259CA8F47711626563E29C /* RelativeFormatterLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0246CF6ED39085AD58A2A4C18931E134 /* RelativeFormatterLanguage.swift */; }; + 05B82D21BB4500108A38518E64D25A6E /* CPListItem+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66B6DFD66EDD8490CD94EC55E8E7B64 /* CPListItem+Kingfisher.swift */; }; + 07F83DE63FB5CC8015F48F7B9B800B6F /* IQPreviousNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC76090E209131CC4AED42CFFCFEB4BA /* IQPreviousNextView.swift */; }; + 07FD274BC01F578C7B37989F79350C1E /* IQToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65814C78FFC0F5199CE49EAC7BFD63EF /* IQToolbar.swift */; }; + 080B0A6E64DD22DBD5BF269AA6A556FB /* KFAnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300F57DE95241E7D9CB929D3B2264440 /* KFAnimatedImage.swift */; }; + 081815B67871C182C0D337274DEDCE69 /* MJRefreshBackStateFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE8B8F2FF82C76A9EF65784FB0C5A6E /* MJRefreshBackStateFooter.m */; }; + 08593ACC3F8955B57D3865FBE5940230 /* JXPagingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967F95BF5036324BE9254F76C8F11590 /* JXPagingView.swift */; }; + 0874E6176184E3A1C3E8AB158AE5E98B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE492A9624C33092DFCBC183ECADE79D /* CoreGraphics.framework */; }; + 09871A1E8CE997DD91E7ACE2A0A273BF /* Zones.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE514086343B4C0D395517B412878058 /* Zones.swift */; }; + 0A5C290276F7453D9471E25BEA655842 /* ConstraintLayoutGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C642D426A1D3F5335387C32BBF5A18 /* ConstraintLayoutGuide.swift */; }; + 0AD7A0DA8FF5880E3D8599062129AB12 /* ConstraintMakerEditable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AE6589CF9C3283A65A9CCFB4A863A16 /* ConstraintMakerEditable.swift */; }; + 0CC41A7F3CDAAF90C1825DCF4FB71B9B /* DateRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A6048835ACC3364C18CAD1AF32230E /* DateRepresentable.swift */; }; + 0D665B6767B345D8C70D7E029A2A48D8 /* ConstraintViewDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BEC05992BEE08743A5630C5E36E343 /* ConstraintViewDSL.swift */; }; + 0DA9A9D2F8960818017E26DA480FA143 /* ConstraintLayoutGuideDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D0D0C744FF259ADDA859BD13CB68F5 /* ConstraintLayoutGuideDSL.swift */; }; + 0F1D68554CA1AC595168E8FB4E1A6E63 /* CachedResponseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D72C68E0B3FBF0A135880692E6E33 /* CachedResponseHandler.swift */; }; + 1093AAD9CB7394BDBDD0E287103515F6 /* MJRefreshNormalTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = C01605AC5DD0430BA05B21A288DA50C1 /* MJRefreshNormalTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1102439674F0EB249240589D2607B9EF /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7469843EF11B7651EAA26E55E112E349 /* Result.swift */; }; + 1159DF94A7740E8F0DC24481E1880378 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6BEB2A0F222387786538A58D8C49C93A /* PrivacyInfo.xcprivacy */; }; + 11F2B1DF419EC68AD10D2A1D1176B082 /* SwiftDate-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C865CCD1F85473AC6AEF3AF846C60A5 /* SwiftDate-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 12EC6890A8A59536867A18E8F83C8BB7 /* JXPagingView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CF39A9D25C909159253007F3C2573326 /* JXPagingView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1336E9B0D82E5DE4E4DA4DAD898489BC /* DateInRegion+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9805317D96D305A7B55B34F9ED5D7FC5 /* DateInRegion+Create.swift */; }; + 13CC0CFB1C56F23BBC872801F983C1A3 /* ConstraintLayoutSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AA948D61BC9D3D35F9604E5A3BC412 /* ConstraintLayoutSupport.swift */; }; + 15CC896E2126D6A47D03C01964D74776 /* JXSegmentedIndicatorGradientLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C74B3D86D767E3958819BDDB455791E /* JXSegmentedIndicatorGradientLineView.swift */; }; + 15F68E09ADCDB843B938C490059485BD /* ConstraintMakerRelatable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE48C9953074AD1749565386F58D202 /* ConstraintMakerRelatable+Extensions.swift */; }; + 162B358A9CC48A414D6744BAF8562D93 /* JXSegmentedTitleDynamicConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505ACEC8786DA864009CAFAD8D7ACC74 /* JXSegmentedTitleDynamicConfiguration.swift */; }; + 165B4E7F47A4A27EDF01B892B8E2A03B /* Array+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89A5C6D1F8C391EC0BA7A85FA520C894 /* Array+Safe.swift */; }; + 16E5F9C7FE2C499D21F3E7AF1FFC2FA7 /* IQKeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD260DDFBA4FDA84F5C6A25B2CB47470 /* IQKeyboardManager.swift */; }; + 170E0C2556FD00466C155473B428F792 /* MJRefreshBackFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = F8DE10CA8E086617945517225C8F1E89 /* MJRefreshBackFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 172D6F4BDBFD921ABF3F4936747C7B5F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 1892ACC1F1247808BB4F54C8A11FA93B /* FSPlaylistItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 095D58DA4F832B26482730E74AA23D62 /* FSPlaylistItem.m */; }; + 18B137C1708A9B9DC45BBB1ECF1CE4A8 /* MJRefreshHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = AD2627618EECADAAD4DB704429147B41 /* MJRefreshHeader.m */; }; + 19C5CA44D4D4D5B711D33A73525D453F /* IQUIView+IQKeyboardToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1D98A6ED81F7F2FFE1483C15157751 /* IQUIView+IQKeyboardToolbar.swift */; }; + 19E363D60D0FD878CFAAEF97EB99209B /* http_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E3E54AA8ABB092B11F44C7EAFB4A20B2 /* http_stream.cpp */; }; + 19FE9BC7F25FF5D48B6B2B3B520D4D47 /* SVProgressHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = BFF72AA8AFE7B31BDC964105FF4B8FF0 /* SVProgressHUD.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1A39B80A5083C84CF22A0D9FBB27761E /* UICollectionViewLayout+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 886F4DF873F4332C1BE04E6A33C70192 /* UICollectionViewLayout+MJRefresh.m */; }; + 1A56A2E0BE0367DDB6BE95E537D43314 /* Commons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918BA156001C627978B12B0E996C4789 /* Commons.swift */; }; + 1AEABE21257723E2AEE836117F3A8B12 /* MJRefreshFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 0DB1F6055B6597CA3AB467E151EFF785 /* MJRefreshFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1B4B180E84924CF99663817A2AFFAA92 /* IQKeyboardManager+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF965E29599DE698FC4901EB4D10794 /* IQKeyboardManager+Debug.swift */; }; + 1BDF02A8680914F05F4D8C2DDB44A9DE /* input_stream.h in Headers */ = {isa = PBXBuildFile; fileRef = 98F57077D04460183803BC6B5E9765D7 /* input_stream.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1CA0950E4FCEAADB6C612F032B72BC3A /* stream_configuration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1FD60AF28AF4C21BAC2E0EF16B4A52 /* stream_configuration.cpp */; }; + 1E7E37C51375285EA22294CD2ADB3159 /* Debugging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98D2AD7AD0CA81C7FE5D1D243FE4573E /* Debugging.swift */; }; + 1FF54BA1EA87ACD81339325012E2537B /* JXSegmentedTitleGradientCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D736329CAD81C67C2A8FC1F582A7 /* JXSegmentedTitleGradientCell.swift */; }; + 202477A9BCD84C01BCBD8C02417ADA1E /* caching_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ADE694B2F5E068593CAAF87E546F52F1 /* caching_stream.cpp */; }; + 20F437D81954DB9DF1BA404A1C48EE5D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 222B9B50C37D319BBACE8E15B8A40348 /* JXSegmentedNumberDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 819F7D2E500376ED4EE8196C862FB5BE /* JXSegmentedNumberDataSource.swift */; }; + 22FAFA41450EC40132CF4B0EEE7E6788 /* ParameterEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE9B2D284160E9548EBF96125E9B7554 /* ParameterEncoder.swift */; }; + 23277D4B1F4E8335DDCAB69A2AD75C9F /* JXSegmentedTitleOrImageDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72A654D4A98DED2922D1F9F69A777034 /* JXSegmentedTitleOrImageDataSource.swift */; }; + 23B1B9EA25E2C943FCE5EC6D4EE19D6A /* MJRefreshGifHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D010AF7D78D6C152BA967EB3141BFF7 /* MJRefreshGifHeader.m */; }; + 23EE2A5A9ADF9A6B9A3515CA85E382ED /* UIView+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 78E348898C88C370F77ED61FD69CB79C /* UIView+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 244119912087C6ECC078CB0E740833EB /* UIScrollView+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = E79F8ECDA1859613039CD7132B7282E8 /* UIScrollView+MJRefresh.m */; }; + 2480B116D9A738E79212C0E0DD41872D /* GIFAnimatedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF22CC8BEABDCD1C073ACB08C8C0EFE3 /* GIFAnimatedImage.swift */; }; + 261D92C1B19B41CB1899009080FEF6CD /* FSParseRssPodcastFeedRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E18B8DB3629DE5ABA672D2EA056DA4FD /* FSParseRssPodcastFeedRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 26541C9BEC53081C7AD9BB9819D71B41 /* JXSegmentedNumberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C43593ADD566299B8A9B4AC5DD8EBB0 /* JXSegmentedNumberCell.swift */; }; + 274E0D56AEB27C391FC58EBED912F19A /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABA7A1E43832CC4FDC1B0589A531B91 /* Image.swift */; }; + 279E3C4211663A097FFE575249B19BC3 /* SVProgressAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = EBB6A03D1FED0558B0BA55987FF67D67 /* SVProgressAnimatedView.m */; }; + 287D870088725A42B4DF1FFC8772EA19 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D907265F596152E9F2094822FF73E4 /* Task.swift */; }; + 2927AE446998C11370D49E4A2CFD17AC /* MJRefreshNormalHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C04292B44287517AD7ABDAF1E52CD95 /* MJRefreshNormalHeader.m */; }; + 2AE3EFDE302E34CF498E6A693ACC9D06 /* ImageDataProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F5FCDB146537A5FB81A9608C55800C /* ImageDataProcessor.swift */; }; + 2B1AAA73D9231A2A7BA89DD1FF01C273 /* JXSegmentedTitleImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E1B99970D923DF6228679CB6A465895 /* JXSegmentedTitleImageCell.swift */; }; + 2B230B24827053BA3E9DA0C78A796BC2 /* ResponseSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883227B7FBBBCE19FA3C9DABB6257BF5 /* ResponseSerialization.swift */; }; + 2B42D035AFF52D62722161A7772C6C08 /* HTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD1DF6883602FF4485A0854AAFB608 /* HTTPHeaders.swift */; }; + 2C4C08BB733A2101D945E8C37256F78F /* UploadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B5329122F0A45061FDFBD241C1F414 /* UploadRequest.swift */; }; + 2C94C305900E95BDB9296A7EFCF3C940 /* MJRefreshConst.h in Headers */ = {isa = PBXBuildFile; fileRef = 379586CA36717B53DDF60E7C554E84C3 /* MJRefreshConst.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2CA3365CAC025B79EF68EB13F395DD61 /* Pods-MusicPlayer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 137651D4B8A2CAB9ADEE7E77FCB50B0C /* Pods-MusicPlayer-dummy.m */; }; + 2DD965DE434BE3B5A0C6EC1FC47AE81D /* String+Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27D37A59036A1116558DA573382EA9F2 /* String+Parser.swift */; }; + 2DDEFA6221FE75D88FE08E78DB380D16 /* audio_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 18406CC620E7BB6D62552425CDBB8830 /* audio_queue.cpp */; }; + 2E572B53624BB2ED444BC9747A9F1781 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17471A60400FF41CA4D77544B7817005 /* ImageDownloader.swift */; }; + 2E84CD435150EDA1356EE23EAB3CE5E7 /* SVRadialGradientLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 54B4FA920379EF013B60907CCBEA03F8 /* SVRadialGradientLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3013EFF5D3DB5F626FB63C96A690082A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 3031C92E9CB81AD378483755A4DA6F79 /* KingfisherManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9907E83DA970901BFCAAF3A5FDCAC9ED /* KingfisherManager.swift */; }; + 3035E05E0216ED11647680F9C2127B01 /* MJRefresh.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 882F435FDF3E0CDABD59D7FEF9958329 /* MJRefresh.bundle */; }; + 30DBC87A501715FE2328B9FF011F1BB9 /* MJRefreshStateHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 50C5A927AB7AF95FB88E19C10BB2C621 /* MJRefreshStateHeader.m */; }; + 32AD6316A2024235B39F19FC97C0B6BF /* ImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CD589D0CF5798FFB6E46700B50175E2 /* ImageDataProvider.swift */; }; + 3360B725B480679D59E44B812DD8D116 /* MJRefresh-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A3A7AA48C71521A2AB98871FF57BE8 /* MJRefresh-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 33882806FF45112A39D7F76DABA51A8B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3FC20D0CF5D2FFA24D77CA142F23498C /* PrivacyInfo.xcprivacy */; }; + 344BCC1443F520E4175B39B77000D2C1 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45FF34A749D6457211D8DAA92C371E13 /* AudioToolbox.framework */; }; + 346855A32221981DE16BBB5C1E636BA4 /* MJRefreshAutoStateFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = E3907D2AE7D5DAB04782E32C936D100E /* MJRefreshAutoStateFooter.m */; }; + 360F2B06AC4C38F3994965AA69FF0AC7 /* Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA09510E6E35CC1A196B0830CF9D6F9 /* Kingfisher.swift */; }; + 36C78069A72BECAEB66B31FF794A09ED /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41D2A7A53A251B9A384116E9720D856 /* Request.swift */; }; + 371D974865C3554FFC4B2D487D12615E /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85976525DE544B30C96C2119D21D3244 /* Notifications.swift */; }; + 37289CBB73D25EA3424BF2ADFBB898AC /* TimePeriodProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 453A74195009C301A029E95CC589FB2F /* TimePeriodProtocol.swift */; }; + 373502D50A1B2266D86409E19EC46DDB /* IQTitleBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AD190CC05A72112FDFB974A611F803 /* IQTitleBarButtonItem.swift */; }; + 37D302D5A81F84194396C4A7C871F1DA /* langs in Resources */ = {isa = PBXBuildFile; fileRef = D7F6101931EF4D62C239843C57A2C791 /* langs */; }; + 39EAB37ED329E6CE7C691B9EAF731446 /* JXSegmentedDotDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A720F6975F8168D2D97AFD10E7D7C3 /* JXSegmentedDotDataSource.swift */; }; + 3B2744E7B2A5C2AD33797BD2D280C8EF /* http_stream.h in Headers */ = {isa = PBXBuildFile; fileRef = D95BC8DF257D6DF9215BC495770135ED /* http_stream.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3B573F7FAA3A12BA7BF2CC874A31DEAB /* DateInRegion+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39BDFBFDF3DC43026E7C749C9D7E00A /* DateInRegion+Components.swift */; }; + 3B901DF82C6576019330391D0F470B31 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 543DBF6743B34366D2E9B3D55471D3AF /* CFNetwork.framework */; }; + 3BC51136C1F65D6962C6727889AA942E /* String+MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64506C7AE6F75CC4AB585A017D14BE73 /* String+MD5.swift */; }; + 3C425FEE22F5E6EE82500F6107EAE8F4 /* JXSegmentedIndicatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3A75B3E2D24B0E349B5094ABCB55CDC /* JXSegmentedIndicatorProtocol.swift */; }; + 3DAFEDCDC5094007E329D71FE84FF704 /* SnapKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C48D633A21657F503CDC4FC9CE8A949E /* SnapKit-dummy.m */; }; + 3EA3F347405C2C463842DC20333121E9 /* id3_parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5984A98BF9B928B0D0AB3A27CF8809CE /* id3_parser.cpp */; }; + 417EAFB7648F1C35B8F7ADC203A96315 /* JXSegmentedBaseItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E648B07B2374E25354F7F0A3630DB640 /* JXSegmentedBaseItemModel.swift */; }; + 41A536E6CE1C85116A756B710B2C660B /* IQKeyboardManagerConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF55BDFE48613A40A1EF055E76F6481 /* IQKeyboardManagerConstants.swift */; }; + 42B43A15ADA9F66F4E2AD66666943F26 /* SwiftDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 884C2A1738B60FA764F7F9E770737AF2 /* SwiftDate.swift */; }; + 4318466387894387E637747807B70757 /* Tiercel-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = EF4708D4F1BBD4A3AFC205C27DD2D187 /* Tiercel-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4337931D8B8E3F3BA03C77C1B496BEAD /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A44A5FC1159E231EEC8EA3A905A120 /* HTTPMethod.swift */; }; + 43E7BD44853E62096917DAF77B6C1CE3 /* Executer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0F024C688B9208EE21BA6A338CAF16 /* Executer.swift */; }; + 43ED71634C98518E6B0749001ED89C43 /* MJRefreshAutoNormalFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8133961A1352A59C4E8DBE54C15E2498 /* MJRefreshAutoNormalFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 440C0830E1410552E2ED0A1647CC4209 /* JXPagingListRefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B8B7C727D8E6DCB02966F19E4BDF14 /* JXPagingListRefreshView.swift */; }; + 4413F602F8263A6E3EE93517916F2EDF /* SwiftDate-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D78E1767DF6AFE67C373D115418CF336 /* SwiftDate-dummy.m */; }; + 45131830DC22C22B4A21C4A54A147947 /* MJRefreshNormalTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F09B7EC32CFE88B4A26B887D4166EAE /* MJRefreshNormalTrailer.m */; }; + 45ABFED1A9F6C0B330BB822AC08029A5 /* TiercelError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916A56D8864031F453F9C00A745F32D0 /* TiercelError.swift */; }; + 45D1C5B966D0BE59EF67F8A839AED2F4 /* JXSegmentedViewTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 921FCA711D3B8BFABCEB7BFD07F64831 /* JXSegmentedViewTool.swift */; }; + 465D662A98148B3FE2CDCD0562AA2575 /* Pods-MusicPlayer-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BBB6DE3AF0E150160FD2FA346CC6CD6 /* Pods-MusicPlayer-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4796934AE90BD750DECD38AD4EDFBA5F /* audio_queue.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CF9D0C18975B91BFAC8937997951692 /* audio_queue.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 47B7A22D43797E42FCDCD96028823BFF /* KFImageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEAB2E073749D67141BC95939FD1DAF9 /* KFImageProtocol.swift */; }; + 4803DFE6BE7D9A926811385567288A8C /* TimePeriodGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB693B14BC2F5BB232B918BC82C24C8 /* TimePeriodGroup.swift */; }; + 48E83D4BC4A5C3542CB0560A1E82A2F4 /* MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 4201EF3E35074620F075BCE228F7AEE5 /* MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 498FFEC31A02476F4F719EF645AC1562 /* MJRefreshHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 382BC06430FA8EEAEE68E10A5501E0ED /* MJRefreshHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 49D5506D651C7F3E49D8DA20ABD46AF9 /* OperationQueue+DispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D95580228CC0BEE1E0AFD5C45A81D41A /* OperationQueue+DispatchQueue.swift */; }; + 4AADD25AAE104D5F72CDC039E82D9BB6 /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF7965FCBF7F70C88E24B4951BB1A7C6 /* RedirectHandler.swift */; }; + 4B6EA5410A2C492D91A141426FD81588 /* WKInterfaceImage+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319CEE88A0DEACE6042548814209CC59 /* WKInterfaceImage+Kingfisher.swift */; }; + 4BB69F4BB4D02CE3DEA258E2F86E46ED /* FSAudioController.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F04E5AE92F6C4FC3ECD3B58E312ED72 /* FSAudioController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4C1CC731DAB49793158DB4EA0982137C /* ImageBinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A9FE5CC92E229E62CDC676046989D88 /* ImageBinder.swift */; }; + 4D77099177C62FCD622E0BB73237386E /* CacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F8DEB6C11CB3F37C7618260A212B87 /* CacheSerializer.swift */; }; + 4EFCD7DECB92938B822DE9FB6CABD30F /* FileManager+AvailableCapacity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA6C3A65958047C1AA995415E6FDD3C6 /* FileManager+AvailableCapacity.swift */; }; + 4F16449E12C5246DC277899C7C5C2262 /* file_output.h in Headers */ = {isa = PBXBuildFile; fileRef = E54FE0BF84BDF5E90E2CC8A3B504BE3D /* file_output.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4F2F7AFD7C593DAE210F77E7F8BC2403 /* JXSegmentedIndicatorTriangleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61195B0A03C35A1089FEF5FA4E466DB /* JXSegmentedIndicatorTriangleView.swift */; }; 4F4B9DFA352D9958C7494D7BC24631D8 /* Alamofire-Alamofire in Resources */ = {isa = PBXBuildFile; fileRef = 085DBCE7DD98588B2ED103B1C1F36026 /* Alamofire-Alamofire */; }; - 4F80144C5D33EFDAC14379838CF9720E /* MJRefreshBackFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 136FDCF413CE3C1DC00030DAFFF9A94F /* MJRefreshBackFooter.m */; }; - 4FE35DBCEE3E9B3AE786452DCCD1B66C /* JXSegmentedBaseDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 460F21D69A6B1F9493AACA08A2F865B9 /* JXSegmentedBaseDataSource.swift */; }; - 506C1EE2968687F178BB2DFA34D185CE /* IQKeyboardReturnKeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCBF5F15899BDBF63D3CC736990E1A5 /* IQKeyboardReturnKeyHandler.swift */; }; - 5075DC82A63A9807DFC390B4CE8046CD /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9A6B3FF123B6733156B839AB90A488 /* Session.swift */; }; - 50E4E24710A4C5A77BE6594A10DFE773 /* ConstraintMultiplierTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1294C0C419D6967DAAFC5C21609CAAAF /* ConstraintMultiplierTarget.swift */; }; - 514A19E702520E6E336D77E2615D17CF /* NSBundle+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = 76BE0C86A979CEA47E0B7AD2EE15D888 /* NSBundle+MJRefresh.m */; }; - 530FC2CFAAF9C09C0B75CF5CF6DF0B65 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 321C97AD92F4D32E4E33BB5B77FFE0FC /* Storage.swift */; }; - 539E9EED5FBF53E3ECF2652ECFA4A2FD /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF75314341E15EAF5EC8A5095F2878D /* MemoryStorage.swift */; }; - 56987A745C2D39979A167EBF45B8D073 /* JXPagingSmoothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142F33BE61F0E752B876C5145BE8D812 /* JXPagingSmoothView.swift */; }; - 56CD198002D3D45274F6719725B983CF /* ConstraintOffsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E17F9E27F1D47AFFD1AE176DFE1657 /* ConstraintOffsetTarget.swift */; }; - 56E561C1A8960D630D418FF931D36FBA /* JXSegmentedTitleItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF28FA874E18F2693E3B035356382F0 /* JXSegmentedTitleItemModel.swift */; }; - 571142A2747CD90165F631A8AE980A0A /* Alamofire-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 56263B06F3EAEC382F6F91F1599D4E74 /* Alamofire-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 574450219BF9DBA9113E31EBC80BAEF2 /* JXSegmentedTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F86EAA1D247756258692097BEA95A21 /* JXSegmentedTitleCell.swift */; }; - 578833D3ED755D5D1F1577005AEADBAE /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = C468581B4214846A9CA4FFED32F836D8 /* TimeStructures.swift */; }; - 5830C6260CA2B7CD6DC74054FB29CDD1 /* DownloadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4316C38FD6E88F2FFBBB3BC6DF2C26FF /* DownloadRequest.swift */; }; - 5896E7040BD3FBD6C57C9727F706407A /* GraphicsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0A7FCD71890676CE08D3ADDB7CA7B4 /* GraphicsContext.swift */; }; - 58E936B1E7E42C0BFC119D428B70F1D8 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5347F4D40BA296B41A792189CB1331E9 /* Alamofire.swift */; }; - 5AD4E5B4118A1DC7D639F611044B4159 /* DataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E279BCC1C2C3A2FA64E3E7F3FD5335 /* DataRequest.swift */; }; - 5C041C0F3BBC8ED3017D03D1A00BFEDF /* DisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7694CB3A399B0E568F03F27D28868B95 /* DisplayLink.swift */; }; - 5D2F784CAC2EEE09B332245A61D3687D /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 475A4FB59CFB3BBA7FAC2F058F91F6E3 /* AssociatedValues.swift */; }; - 5D4490556ADDD36F5100CF5D9F68ABF9 /* Pods-MusicPlayer-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BBB6DE3AF0E150160FD2FA346CC6CD6 /* Pods-MusicPlayer-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5D90B0097BBDAB6B90F35314BBC37554 /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594D2794764B5796C8A422C4401A7E2F /* Date+Create.swift */; }; - 5E858AD3F64F096EE17F34759F017DC3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1199E580C5F19AFD1010FE20258DADBD /* UIKit.framework */; }; - 5E8E9469BF126E918EADF79EC4BB5CA2 /* IQUIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9045FA6D60732F49E7765B398DDC46B /* IQUIView+Hierarchy.swift */; }; - 5E977209B51A440181A31A4CA27CA677 /* ImageContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D061CC1A5FB61D63B44C75541D898A36 /* ImageContext.swift */; }; - 5F52D18FE8736028A8FB4B4B61B49111 /* JXSegmentedView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 356BE641A77A6E56A217190DD89D8259 /* JXSegmentedView-dummy.m */; }; - 5FE04B779A038EBDFE268D19297F0FA5 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 728D0FC1713F8BD85AD5D78DF7CC3E06 /* RelativeFormatter.swift */; }; - 6084CC72921FA7260B8FA2A07F49AE96 /* JXSegmentedTitleAttributeItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDEAE488C153F23CC530F9FC7878CE4 /* JXSegmentedTitleAttributeItemModel.swift */; }; - 60CD99E8F32FD54F599DECFB768863A0 /* JXSegmentedIndicatorImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E47356E556750015100B2119302E53 /* JXSegmentedIndicatorImageView.swift */; }; + 4F80144C5D33EFDAC14379838CF9720E /* MJRefreshBackFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 816DB2F04B71F5F810AE6A0B24C8CC71 /* MJRefreshBackFooter.m */; }; + 4FE35DBCEE3E9B3AE786452DCCD1B66C /* JXSegmentedBaseDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBEE13981AE8C02C5198ADE53051B6 /* JXSegmentedBaseDataSource.swift */; }; + 504BCCF03618854351F39813039AC5CA /* FSAudioStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 2516E789B118A50B5D0289D7464D36EA /* FSAudioStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 506C1EE2968687F178BB2DFA34D185CE /* IQKeyboardReturnKeyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA15F6CC688D6CF6096FEAB9906F86F /* IQKeyboardReturnKeyHandler.swift */; }; + 5075DC82A63A9807DFC390B4CE8046CD /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77C16D3C7CB62A673F650B8575E7221D /* Session.swift */; }; + 50E4E24710A4C5A77BE6594A10DFE773 /* ConstraintMultiplierTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50068CE99A1D5B57ADA44E975763524F /* ConstraintMultiplierTarget.swift */; }; + 514A19E702520E6E336D77E2615D17CF /* NSBundle+MJRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = B12082E67BEB2172AFEE6485633D64DF /* NSBundle+MJRefresh.m */; }; + 530FC2CFAAF9C09C0B75CF5CF6DF0B65 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E978BF5057345D65521CCBC6E7B4D394 /* Storage.swift */; }; + 539E9EED5FBF53E3ECF2652ECFA4A2FD /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454DD3800F47E80B70947B50B5EA6B4A /* MemoryStorage.swift */; }; + 5610B3D1D3CC41790D99E354C1201DA5 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3B19C6050B49E5E5B56765AEF5FE5F /* Cache.swift */; }; + 56987A745C2D39979A167EBF45B8D073 /* JXPagingSmoothView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1399E47A9873040FC5D3A42F382917 /* JXPagingSmoothView.swift */; }; + 56CD198002D3D45274F6719725B983CF /* ConstraintOffsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87A37B7FF63CC268718D512871DDD82 /* ConstraintOffsetTarget.swift */; }; + 56E561C1A8960D630D418FF931D36FBA /* JXSegmentedTitleItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3C1F981C5074CEFE799E99CBAC5B3 /* JXSegmentedTitleItemModel.swift */; }; + 571142A2747CD90165F631A8AE980A0A /* Alamofire-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5142C85EC3B111E46F791033767E12E2 /* Alamofire-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 574450219BF9DBA9113E31EBC80BAEF2 /* JXSegmentedTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15CA5A8DF5A4A18FF5C1D85FDD30CC6A /* JXSegmentedTitleCell.swift */; }; + 578833D3ED755D5D1F1577005AEADBAE /* TimeStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AD76C863D0635308D9E71629E36156 /* TimeStructures.swift */; }; + 5830C6260CA2B7CD6DC74054FB29CDD1 /* DownloadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 272E28E27BC34E8A01009E5AF1BFF723 /* DownloadRequest.swift */; }; + 5896E7040BD3FBD6C57C9727F706407A /* GraphicsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE510A9C1FFF14CCF35D6CC112249B7B /* GraphicsContext.swift */; }; + 58E936B1E7E42C0BFC119D428B70F1D8 /* Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82ABE0C8114FEFA9A19E7474E07C72B1 /* Alamofire.swift */; }; + 5AD4E5B4118A1DC7D639F611044B4159 /* DataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76603E85B6156E967FC2F4A6E2651206 /* DataRequest.swift */; }; + 5C041C0F3BBC8ED3017D03D1A00BFEDF /* DisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB13925982168A575D343E753CD1070B /* DisplayLink.swift */; }; + 5D2F784CAC2EEE09B332245A61D3687D /* AssociatedValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ABD14F50DD9BE7BAAB5492C0D117A8 /* AssociatedValues.swift */; }; + 5D90B0097BBDAB6B90F35314BBC37554 /* Date+Create.swift in Sources */ = {isa = PBXBuildFile; fileRef = B06F8788675D48A6E7D3FAEDFB453AAA /* Date+Create.swift */; }; + 5E858AD3F64F096EE17F34759F017DC3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB406B5AB28CD8F5747EBE9498A2F869 /* UIKit.framework */; }; + 5E8E9469BF126E918EADF79EC4BB5CA2 /* IQUIView+Hierarchy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE49DC44DD670EFE086D67A7A64F08A /* IQUIView+Hierarchy.swift */; }; + 5E977209B51A440181A31A4CA27CA677 /* ImageContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110F64C4F2FD9C3DE754C0DF6D6BE4CB /* ImageContext.swift */; }; + 5F52D18FE8736028A8FB4B4B61B49111 /* JXSegmentedView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 73116E04CFC77FBAC5754FB3135DD50F /* JXSegmentedView-dummy.m */; }; + 5FE04B779A038EBDFE268D19297F0FA5 /* RelativeFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D4BE2E3AB761AADDBACFB7F2B3FC96 /* RelativeFormatter.swift */; }; + 6084CC72921FA7260B8FA2A07F49AE96 /* JXSegmentedTitleAttributeItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B9BF1E5F3DBD4B648391B867DC65F5 /* JXSegmentedTitleAttributeItemModel.swift */; }; + 60CD99E8F32FD54F599DECFB768863A0 /* JXSegmentedIndicatorImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6BEFA8047C293D703F2D4271C95AF4 /* JXSegmentedIndicatorImageView.swift */; }; 60CE1AC69D95F0F7C6637C601A84C069 /* MJRefresh-MJRefresh.Privacy in Resources */ = {isa = PBXBuildFile; fileRef = 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */; }; - 612AE0ABB9BCD3AF0E1D29B4C063CA62 /* StringEncoding+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = E681F9A4BFBE0245493029CEB1B8A1C8 /* StringEncoding+Alamofire.swift */; }; - 6142372CEE4D251A94EA4BE2630F27E7 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C0D566F3A17E2E2DBDE4098F711125 /* Date+Components.swift */; }; - 632239BD2DF58FF13D9E4D1C4327F528 /* JXSegmentedTitleDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E652FB17C57B0AF0273621B2B037A0C7 /* JXSegmentedTitleDataSource.swift */; }; - 638A729C295B6267093699D505B8589D /* KFImageRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43CFD3981B70FD7D609A5839B6A9218 /* KFImageRenderer.swift */; }; - 63BCE3414C785046BB317537B8120B5D /* MJRefreshAutoFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 94F9116CE6A49B5AFFA19B16D5346243 /* MJRefreshAutoFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6431218578A566C5EBF5FD166F164059 /* ConstraintMakerExtendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC53E7C517308EABC44AEFB75F5C064 /* ConstraintMakerExtendable.swift */; }; - 647D16586EBBE25158E3FD684541A1DD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - 64B7C1BD8191B0963F3897F64977A7AA /* Pods-MusicPlayer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 137651D4B8A2CAB9ADEE7E77FCB50B0C /* Pods-MusicPlayer-dummy.m */; }; - 6584A733B2610DAFBB1CEF8E90635EC9 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AB27763B449AF051985734B3773B4ED /* TimePeriodCollection.swift */; }; - 65D2E717EEAEBB4658708CD9C8991C93 /* CallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1CCD164ECD3A398893DB0C3F224494 /* CallbackQueue.swift */; }; - 65F1A046F742E910CE0EE30BE2600DDD /* JXSegmentedDotItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CFAC3CD688A5BEB743550BA40C464FF /* JXSegmentedDotItemModel.swift */; }; - 6628BE82C4F27A39F94CBEA02BB498EF /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B33B085086724D9914992FFDEF83E0B /* DateInRegion+Compare.swift */; }; - 6654CDCA10823EA080976DF72D8729B2 /* AVAssetImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43F88826BD8118C95446B4D9D4F416F /* AVAssetImageDataProvider.swift */; }; - 6735E4B11C64D20B4E05A92811F5A721 /* IQKeyboardManager+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 773DD03EC2EC00F685DE0E46779320CC /* IQKeyboardManager+Internal.swift */; }; - 67942FD70EE19BA957045141036FD49E /* RequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035EB38FC9B73D3AFE4C25920C1A1C32 /* RequestModifier.swift */; }; - 68238227D42B2511FA6A26BF71E92520 /* Alamofire-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AA35F74EEBAD4E0CB28C015AD5D6EECF /* Alamofire-dummy.m */; }; - 68241B77CB3EE81665BBB4275AC19679 /* JXSegmentedListContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA90CEFF373663026D2F06371A3D4E9A /* JXSegmentedListContainerView.swift */; }; - 686ABAEBCE5C0EA6644AA80F9B4B3687 /* MJRefreshNormalHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = E4678D7C77B6546A4205C194E9F275EB /* MJRefreshNormalHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68995B28EE5B539CEA5A1133E4623927 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB03A8FE1983F5F495EC9ADC0D6E7E6B /* MultipartFormData.swift */; }; - 68A74F13F8FEBAA7E0EA9344DED0458B /* OperationQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB50AF41FD17FD4D634D6CB5152810D /* OperationQueue+Alamofire.swift */; }; - 68DCF4550B3B72228D5D8B4E25A341F2 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56CF6C9E5C4416011FAC32CEB31AA525 /* Box.swift */; }; - 69261B5D3B53EBF7109D5E1DA3768CAC /* AuthenticationInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7052242B5B59DCEC7125EE873BC0202A /* AuthenticationInterceptor.swift */; }; - 6C2A8D78B7E5B02974B212599B28AD77 /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1EC547B26205284A72A9F9473EB73 /* Indicator.swift */; }; - 6C4BCC8C2D31263360E2697776A9E80D /* JXSegmentedIndicatorBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E81980AF59D081E0E8A9761D1C422BF /* JXSegmentedIndicatorBaseView.swift */; }; - 6CC7E7C00730B1BF42A28B2E23CA01D6 /* URLSessionConfiguration+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A6ED7989C6F4843507D223BD102CC5 /* URLSessionConfiguration+Alamofire.swift */; }; - 6D389E4D5F97798BE1AEEB8C317852D7 /* SnapKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 31126E0DD1B56D3713965A56F0B05AC8 /* SnapKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6D4A08865846C0F0DB9A05C617C2A947 /* MJRefreshStateTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = B764532F7A465FF640FDA97A850A103D /* MJRefreshStateTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6E2884B29CD49EE91C48C725105F295A /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEE5906F519651F94E70B28F42C4C70B /* LayoutConstraint.swift */; }; - 6E4DEB1A81739DE58C64997251DAA87C /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F56A78070D28571910937BC0EE5894B8 /* ISOFormatter.swift */; }; - 6F271B6EA85E0BDDA7DD1CC621EDC4F9 /* MJRefreshGifHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 536595B34971D58E93F91CEFFBA20A22 /* MJRefreshGifHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 6F2E0DC7D8598283D088A989FDB8E5F6 /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 368AFBDCEBBA20270829ACA09283D127 /* ParameterEncoding.swift */; }; - 7052944C657F270E47777446D0E10E1C /* ConstraintInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 234A9BD48AA1E6813E36334F22C346D4 /* ConstraintInsetTarget.swift */; }; - 70B890CA221D229ABF2346A7245654C5 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD56DAD13E1185FD74DB7A8649E746CE /* Int+DateComponents.swift */; }; - 714D5F4F9165ED6BF3CFA9FEA7DB9FF5 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774F6E017AC64D4F20E0DA360CA3FECF /* Placeholder.swift */; }; - 71655F70079826C7494ABF198F9F563A /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D9FC3ABDA982F6CA82384628D6F165 /* RelativeFormatter+Style.swift */; }; - 71B32D2CE27370DCC6032569FEE5C8DC /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = DED1174C630E86B387EC34B8B1DD2DA3 /* Date+Math.swift */; }; - 72C19C762FADC82517C344E9F47D7E50 /* AlamofireExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8F4312D81FC10BD8029BE851C76498 /* AlamofireExtended.swift */; }; - 72D0CDE3B34D718422CFD4F85ADEFB04 /* RetryStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8AC6801BE5A7B67B8032757817BE51 /* RetryStrategy.swift */; }; - 735B99CA4190FBE5FE23DA796402D447 /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951E048B27AEAC006B779E585701262E /* DateInRegion.swift */; }; - 73F8AC99A58E5837924C056E89543B97 /* Typealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440D319A1267C894C2AFFDA3362EBE9E /* Typealiases.swift */; }; - 74145F14ACD710C1D165C5F9B3F0D7ED /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - 74BF3CC58474F118E0E3953ADBBD233F /* MJRefreshComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CBB6593899E987A5B4300FC21B80983 /* MJRefreshComponent.m */; }; - 74DBD7BAE1B62FA323770957159589E1 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA39C6D2448DC5A9AD18DDA3C96A1A0F /* Accelerate.framework */; }; - 77A016AB014A21D60BBFAB9F7134D38B /* MJRefreshTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = A56C8749F94C4894BDE28709B22BC8EC /* MJRefreshTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 77D782DE125D322922466676E2FFA289 /* ConstraintPriorityTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667B5BDDACE02078C87EFED6AAB723C2 /* ConstraintPriorityTarget.swift */; }; - 78CA17524C270C0E381677E3D4C77B6B /* ImageDownloaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D36A80F29E20B609E647BAFB548A270 /* ImageDownloaderDelegate.swift */; }; - 78D414CF02F5ED61DAB995221C459C22 /* JXSegmentedIndicatorDoubleLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835F2888E352852912F4709F5E54512B /* JXSegmentedIndicatorDoubleLineView.swift */; }; - 794FC38D15336AB502B73B012005E9BD /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA42DDB21EAB3527641F275C3D22E4D0 /* Validation.swift */; }; - 795681285B4E2B121B5CD420131168F8 /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94A8D6D5B3A24A5E000AF4DBE9434CF /* MultipartUpload.swift */; }; - 7A19CD0F168E8C66757012114767A36B /* UIScrollView+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = F0CF839DB998A3AA0D5D01C635E92AB8 /* UIScrollView+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7AD0E5A1A45CE95B2C7A8078B2F19073 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2998944D54451B52A0996B2A9FDDD7DB /* TimeInterval+Formatter.swift */; }; - 7AE2E0B382A14D58BE7DAA0C852DCD02 /* ServerTrustEvaluation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAC3162B7531D025AB197602FE75F31 /* ServerTrustEvaluation.swift */; }; - 7BC03DCFF31FDA534EB7B100209D1718 /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1968FDA9F67DC3333951BED4268CA52 /* Calendars.swift */; }; - 7CC5E76223E1EB7B09FBC03EA07E3DB3 /* JXSegmentedIndicatorBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31B1C576F8187B6B1A1F160B90D6081 /* JXSegmentedIndicatorBackgroundView.swift */; }; - 7E59ABF8A1AC8F61FEFB9B8C72EFB9E5 /* IQKeyboardManager+Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988FB962B33EFE0451CB5B6094DEAA75 /* IQKeyboardManager+Position.swift */; }; + 612AE0ABB9BCD3AF0E1D29B4C063CA62 /* StringEncoding+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 968C21562868FE498570DD48476092E9 /* StringEncoding+Alamofire.swift */; }; + 6142372CEE4D251A94EA4BE2630F27E7 /* Date+Components.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2F8D181C6E23DFEAF1BC524C922FAB /* Date+Components.swift */; }; + 632239BD2DF58FF13D9E4D1C4327F528 /* JXSegmentedTitleDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB06C2DEEDF5BE52555F1C882B3020E /* JXSegmentedTitleDataSource.swift */; }; + 638A729C295B6267093699D505B8589D /* KFImageRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038B18C5D6DE667EE97D03F020423EDD /* KFImageRenderer.swift */; }; + 639CD31D622CB8B75228209AF6ACDA4C /* caching_stream.h in Headers */ = {isa = PBXBuildFile; fileRef = 065328F2F98184B72B3BDFD13B1DE42D /* caching_stream.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 63BCE3414C785046BB317537B8120B5D /* MJRefreshAutoFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 202A71735BE68C199BF37F40C6CE4F19 /* MJRefreshAutoFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 640928DFF857276867C29DF16DF95B0B /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1881FE3DF05060537FF888C35D42D74 /* MediaPlayer.framework */; }; + 6431218578A566C5EBF5FD166F164059 /* ConstraintMakerExtendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839F15386652EA3A10F991169D2BDC8F /* ConstraintMakerExtendable.swift */; }; + 647D16586EBBE25158E3FD684541A1DD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 64B00A7926F992ABED546BFCE00AA268 /* FreeStreamer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = BDBEAB153F0F1BFE8EE317B7794B999E /* FreeStreamer-dummy.m */; }; + 6584A733B2610DAFBB1CEF8E90635EC9 /* TimePeriodCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0A49AFC87CB13825318FB69E4F3964 /* TimePeriodCollection.swift */; }; + 65D2E717EEAEBB4658708CD9C8991C93 /* CallbackQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E74D845B956ED13AF023CCFCFB780E /* CallbackQueue.swift */; }; + 65EEB9B45B77C5733E6233759FE7BC76 /* Data+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = A315721095BED3E19F0DA90AB3FB056A /* Data+Hash.swift */; }; + 65F1A046F742E910CE0EE30BE2600DDD /* JXSegmentedDotItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B8C3FB964677C8073E6FF156C5DBAE /* JXSegmentedDotItemModel.swift */; }; + 661036CF70C0946F0ED7BAF395598868 /* URLSession+ResumeData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C6287F6DCA0BCB29F738E6D22B4F9F6 /* URLSession+ResumeData.swift */; }; + 6628BE82C4F27A39F94CBEA02BB498EF /* DateInRegion+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815F60113B1A64FC31D1FFBBCF1B1505 /* DateInRegion+Compare.swift */; }; + 6654CDCA10823EA080976DF72D8729B2 /* AVAssetImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24AC0CBB36F897360D002FAF35B4331 /* AVAssetImageDataProvider.swift */; }; + 6674DC681C85272A69C0E775CA7F102E /* input_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9E1879F0CCEF6A60749ADDF0C4C21F45 /* input_stream.cpp */; }; + 6735E4B11C64D20B4E05A92811F5A721 /* IQKeyboardManager+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88000352748C51EE5F5C9F5B89E199A9 /* IQKeyboardManager+Internal.swift */; }; + 67942FD70EE19BA957045141036FD49E /* RequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0F4F267A6894DD903B619D88DD4B281 /* RequestModifier.swift */; }; + 68238227D42B2511FA6A26BF71E92520 /* Alamofire-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DBA144AD6F6B2C8309C265DA2BDC2E4 /* Alamofire-dummy.m */; }; + 68241B77CB3EE81665BBB4275AC19679 /* JXSegmentedListContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F813F795ABA741EA4048B038E7EC4C2 /* JXSegmentedListContainerView.swift */; }; + 686ABAEBCE5C0EA6644AA80F9B4B3687 /* MJRefreshNormalHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 7CB1AF83C65AAD9D529E0E732D2C71B8 /* MJRefreshNormalHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68995B28EE5B539CEA5A1133E4623927 /* MultipartFormData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558ADE5127C2037C1373813C4E4DA1D6 /* MultipartFormData.swift */; }; + 68A74F13F8FEBAA7E0EA9344DED0458B /* OperationQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27DAAB9A8B0D734A9BBE8E8F4453F9D4 /* OperationQueue+Alamofire.swift */; }; + 68DCF4550B3B72228D5D8B4E25A341F2 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF6EFC03B1FAE3BFFE25B327C58E984 /* Box.swift */; }; + 69261B5D3B53EBF7109D5E1DA3768CAC /* AuthenticationInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 104108C5D1C6A09D9C7329B2B430CFE1 /* AuthenticationInterceptor.swift */; }; + 6C2A8D78B7E5B02974B212599B28AD77 /* Indicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D7701F20B5E52C4FAFC9907793CFA5 /* Indicator.swift */; }; + 6C4BCC8C2D31263360E2697776A9E80D /* JXSegmentedIndicatorBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA836459635F8DAA87C15B9C0EC55B49 /* JXSegmentedIndicatorBaseView.swift */; }; + 6CC7E7C00730B1BF42A28B2E23CA01D6 /* URLSessionConfiguration+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113456ED5740F97EACFA393659870BA7 /* URLSessionConfiguration+Alamofire.swift */; }; + 6CE562E7A66132DE1C4BCF574ECD3A26 /* Reachability-Reachability_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = DFC89BE171DE7E648C53797695D8A220 /* Reachability-Reachability_Privacy */; }; + 6D389E4D5F97798BE1AEEB8C317852D7 /* SnapKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 77DB14F123949BD939D3F699B4266D69 /* SnapKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6D4A08865846C0F0DB9A05C617C2A947 /* MJRefreshStateTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = 98621F211CCD821D8BED35AE739E356F /* MJRefreshStateTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6E23DA3D58E8C10B007C321E13508FEF /* URLConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64647B22D390B0085715A7A6C6432D74 /* URLConvertible.swift */; }; + 6E2884B29CD49EE91C48C725105F295A /* LayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC171B63D94CE51CC1D237A219839672 /* LayoutConstraint.swift */; }; + 6E4DEB1A81739DE58C64997251DAA87C /* ISOFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82423E826579CCC1BF9A80112E14C669 /* ISOFormatter.swift */; }; + 6F271B6EA85E0BDDA7DD1CC621EDC4F9 /* MJRefreshGifHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = EB6BC0B4BBD642B6ACD05F64DBC1914A /* MJRefreshGifHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6F2E0DC7D8598283D088A989FDB8E5F6 /* ParameterEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27DAB2C811D037E7923BC7DC85E95B24 /* ParameterEncoding.swift */; }; + 7052944C657F270E47777446D0E10E1C /* ConstraintInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C2DC64431AFFDEAB367A051F329841 /* ConstraintInsetTarget.swift */; }; + 70B890CA221D229ABF2346A7245654C5 /* Int+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDA2472F9717357C6BB8F71997F4BB1 /* Int+DateComponents.swift */; }; + 714D5F4F9165ED6BF3CFA9FEA7DB9FF5 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49619FA43727E6B05EFF845F078B07FF /* Placeholder.swift */; }; + 71655F70079826C7494ABF198F9F563A /* RelativeFormatter+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34FBEA0F135C4262CAB70AF8ED88B0D /* RelativeFormatter+Style.swift */; }; + 71B32D2CE27370DCC6032569FEE5C8DC /* Date+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274BEE86492F3C61DE6D50C0A34C9991 /* Date+Math.swift */; }; + 71B478D19DAF06BE8A9C5D9DDEFC7342 /* FSXMLHttpRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 154B43B57C8ADCBE6975B5E4CB8D8BE8 /* FSXMLHttpRequest.m */; }; + 72C19C762FADC82517C344E9F47D7E50 /* AlamofireExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B85D70EFBE6C79981BAA409C3C02D5 /* AlamofireExtended.swift */; }; + 72D0CDE3B34D718422CFD4F85ADEFB04 /* RetryStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8754C6318F74231AB6E36C5F1BD49BDB /* RetryStrategy.swift */; }; + 735B99CA4190FBE5FE23DA796402D447 /* DateInRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FEC0D9F81CB5F7CE5FDE9B82B17585 /* DateInRegion.swift */; }; + 73F8AC99A58E5837924C056E89543B97 /* Typealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28964B0FC810363A9BFD622768788FC3 /* Typealiases.swift */; }; + 74145F14ACD710C1D165C5F9B3F0D7ED /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 74BF3CC58474F118E0E3953ADBBD233F /* MJRefreshComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 81BAB32DB922253ED14C81368B9744D0 /* MJRefreshComponent.m */; }; + 74C30F8C1024E47EB601FF5FE1B05C54 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 543DBF6743B34366D2E9B3D55471D3AF /* CFNetwork.framework */; }; + 74DBD7BAE1B62FA323770957159589E1 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCC96C4E503A8275C142ABAFD3CE0373 /* Accelerate.framework */; }; + 75B1BB0464F873B402FC5103A3AB691A /* file_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9598A6AA81E42757DB35F6F39AF08B4D /* file_stream.cpp */; }; + 77A016AB014A21D60BBFAB9F7134D38B /* MJRefreshTrailer.h in Headers */ = {isa = PBXBuildFile; fileRef = AF9FD0EA190BF89FE270D6BAE52B3067 /* MJRefreshTrailer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 77D782DE125D322922466676E2FFA289 /* ConstraintPriorityTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E038FB2AF6F7D78755CD706F903925 /* ConstraintPriorityTarget.swift */; }; + 78CA17524C270C0E381677E3D4C77B6B /* ImageDownloaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10FFD14C2E11E5F89FF13935005EC4D8 /* ImageDownloaderDelegate.swift */; }; + 78D414CF02F5ED61DAB995221C459C22 /* JXSegmentedIndicatorDoubleLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C878F42BB922E2D8ABB27AB7491FFE39 /* JXSegmentedIndicatorDoubleLineView.swift */; }; + 794FC38D15336AB502B73B012005E9BD /* Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D33BEC74FB7324EE93DBBB16CA1C2820 /* Validation.swift */; }; + 795681285B4E2B121B5CD420131168F8 /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96B33F56346B35C8103DED7E12D113E9 /* MultipartUpload.swift */; }; + 7A19CD0F168E8C66757012114767A36B /* UIScrollView+MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = F4B34772DCB703B04668370DA984408B /* UIScrollView+MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7AD0E5A1A45CE95B2C7A8078B2F19073 /* TimeInterval+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05D488858AA61B9A41D705A0719AB788 /* TimeInterval+Formatter.swift */; }; + 7AE2E0B382A14D58BE7DAA0C852DCD02 /* ServerTrustEvaluation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80011A076CE73595572EF851C7E6A04 /* ServerTrustEvaluation.swift */; }; + 7BC03DCFF31FDA534EB7B100209D1718 /* Calendars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15E6514D83861C620C7A013C94AAB9F4 /* Calendars.swift */; }; + 7C394A568A5D145C94DB767030F96751 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6490D638BB8DD75279D3868652896488 /* AVFoundation.framework */; }; + 7C505E7C30A2E0FA68171866E82652AB /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD66F835AAF00EF7D103A4AA5C1F6E4 /* SessionManager.swift */; }; + 7CC5E76223E1EB7B09FBC03EA07E3DB3 /* JXSegmentedIndicatorBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EB4D3AA3806AA06D95FA6341AFE44A /* JXSegmentedIndicatorBackgroundView.swift */; }; + 7E59ABF8A1AC8F61FEFB9B8C72EFB9E5 /* IQKeyboardManager+Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FA7E2B552FE3E8F4062758D55B2B19 /* IQKeyboardManager+Position.swift */; }; 7E7F1EE3B8C0BB721181929B686FEC17 /* Kingfisher-Kingfisher in Resources */ = {isa = PBXBuildFile; fileRef = C298ABB78D9B05529B89D8322DB2E7B0 /* Kingfisher-Kingfisher */; }; - 7F60A80731DCB0EB26C7D01DCCA932E4 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86134D091916FFC476610F2EBFE771E2 /* ISOParser.swift */; }; - 80244605FFB26BF165B3C6E3CFA83A3C /* JXSegmentedTitleImageItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65D6A9EE341D83823E03C122A72A5C1 /* JXSegmentedTitleImageItemModel.swift */; }; - 8072251E5FFDC1AB89E7558218C41D24 /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864DAEC4F579E88712B7F5B447424FFC /* KingfisherError.swift */; }; - 81DAB282980A3BB8EB7796B61934E4F4 /* KFImageOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDB52D83D152A96E4E9CD244CA95DEA /* KFImageOptions.swift */; }; - 82FE3B046FEA46F2BDFE7FB0E9D7CBAD /* SVProgressHUD-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = C68BBA48149EB58E146B969600FB99B8 /* SVProgressHUD-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 830535E48AA507D7C15359F2A5B72DD1 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4897326E2C8838615713B47EBE1FAD3D /* ImageProgressive.swift */; }; - 83DBC0F86AD7C6EFB6947E0F3616467E /* JXPagingListContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2DC9D938B1784B3E0E8FFC9851FCAEA /* JXPagingListContainerView.swift */; }; - 8423D60239269F191A47A3E2D82E1EF7 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E33A2601884DE73A47BD1A2690B14F /* ImageTransition.swift */; }; - 84BEB9E439780B1E0DEF56459E3D3352 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - 8640829049AE9907FE93825E5510C33D /* ConstraintMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DD91D5AAEBFBFB4776365998B843B7 /* ConstraintMaker.swift */; }; - 868D4A456CBC3255A772F7FEA2F63BE0 /* JXSegmentedIndicatorParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC00130945EBD53372EFBC22FB8119E /* JXSegmentedIndicatorParams.swift */; }; - 8775BC8B188C38085646E9580CE231BA /* Kingfisher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 814205A97B8158339AF6DCF93609360F /* Kingfisher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87996D11DC92EE19EAF9305DAEA4ACF2 /* ConstraintMakerPrioritizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24091B247B320B28B53B7DB2A0EB996B /* ConstraintMakerPrioritizable.swift */; }; - 8868BA37E3CE7C58D26123806D543F3F /* ConstraintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FBC7116157221928733CE2DB074EB86 /* ConstraintView.swift */; }; - 887DB52C63E52FBD3B88F42DD8CFB421 /* RequestCompression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6DA59208C9C680C1A08AB6B751A05E /* RequestCompression.swift */; }; - 891D1BF14C8881C74262EE9DBE5D67A9 /* UILayoutSupport+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16ABD25AB8AAB13E0072CADE42E65 /* UILayoutSupport+Extensions.swift */; }; - 8C077A05101B84731A302657381218CB /* MJRefreshStateHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 86545830194573BF57878FC631A90181 /* MJRefreshStateHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8CEABAE06B171EA941EB497A2F4A6917 /* Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = E75F2A56E77879A823734237F7B0D90B /* Runtime.swift */; }; - 8D18198290C6F15504358DD49F0C505F /* JXSegmentedTitleOrImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8B5D1A6CBAD8FC5DA5D3D0DA7512BE /* JXSegmentedTitleOrImageCell.swift */; }; - 8DD0EA5259D87AA915FC266D43CD08D6 /* JXSegmentedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67318C8CDF97D3763D84C0B08910AD54 /* JXSegmentedView.swift */; }; - 8DD46EE7FB9503E7634E929DDE1CBA31 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6B66846BA638DCD6F5ABB338C4AA41 /* Notifications.swift */; }; - 90441CBC43993A5E4E0F5CD5BECC2DDE /* MJRefreshBackNormalFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = F15F5BABB5166FF00A9E43E67C060454 /* MJRefreshBackNormalFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 906EF891E58A035281766993F82373E3 /* ConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58092F279DEDF69EA7576C313DC62F89 /* ConstraintItem.swift */; }; - 90D847B19214926EDE5210D44A08F3C7 /* RequestTaskMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1665911416D598AAD937FC0E0D42E4 /* RequestTaskMap.swift */; }; - 9136355471B1C08A16DA36B0A2E536B8 /* NSButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B843EC8B993674D3288F87AEA9D122 /* NSButton+Kingfisher.swift */; }; - 9189CD495B78CBC65B25DED32A881426 /* KFOptionsSetter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A26B70DABA1CBACD8166FC00D07C081 /* KFOptionsSetter.swift */; }; - 92138A77DFEB4F76FCB582E97633896D /* URLRequest+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA5D2A634B9432EA862513BDE2A5ED4 /* URLRequest+Alamofire.swift */; }; - 9255A2BCECEB0006DAAF985CA00E2D15 /* JXSegmentedTitleGradientItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E39692FF9A73D5EC6D4AE2DE35E3C0C /* JXSegmentedTitleGradientItemModel.swift */; }; - 92A81311D8592CB3D907E9DA59023E38 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 2C9B6C74D874F93335EDA5FBAC20675C /* PrivacyInfo.xcprivacy */; }; - 930B495B7A3197730A016E2339A4CBA7 /* IQKeyboardManagerSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E1176C58859CF5334183B7786DAC8E87 /* IQKeyboardManagerSwift-dummy.m */; }; - 932470342D7352EEC18C00E52B37A5AE /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000257BCD6570C4B0371D660CCF8D3E1 /* DotNetParserFormatter.swift */; }; - 9378157945D7B405C862A05B0D6B971B /* Result+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0F440BD5C5987BBAD5992F1CC0CCAA /* Result+Alamofire.swift */; }; - 938CFF5F6DE6E7E8CE94373E51F3E07A /* ConstraintLayoutGuide+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B4DCC2536CD7F8BD55F0BAA08F110C7 /* ConstraintLayoutGuide+Extensions.swift */; }; - 94E00674FACB3ED40684E8DAA40BCC84 /* KF.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE633CEBF91C6D351A438EB13D3F4E1F /* KF.swift */; }; - 950B809760A8CE4375DCE1016FB9859F /* ConstraintMakerRelatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86EE2D4250A56EE1BB39FFEB834371D /* ConstraintMakerRelatable.swift */; }; - 9779D52AD8CDA703D6EEE1C6D38E019F /* ConstraintAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0AE0ACE265AEC714180F643F48C621 /* ConstraintAttributes.swift */; }; - 97D7D91FC818805D8344C373CC098C32 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BAE3F45733F6EB3DAE2F48599D8CAB /* SessionDelegate.swift */; }; - 97EA46DA072EC4FD4D831606095B92C1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1199E580C5F19AFD1010FE20258DADBD /* UIKit.framework */; }; - 98455F4176C861F9E33D36892A932684 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - 99F2413E51A57A17AF0B52EE43D448D3 /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B4649E1D11EB9D32BD3E212382EF1D /* Resource.swift */; }; - 99FB2910921988A9BCC5533A1EA70E59 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA38A01E090C908AE5886021034EE11D /* Region.swift */; }; - 9A07F0B734748735A80119550AC32104 /* SVIndefiniteAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DE3D079E059E37BB8233A28ECEF0FC7 /* SVIndefiniteAnimatedView.m */; }; - 9AF14E4B1C52E5AD4C38021C4EC17974 /* UIScrollView+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 96222B976672EC3736CB9B53FC49C92E /* UIScrollView+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9AF53260DACD3DE030C97AA110DAD22E /* MJRefreshComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 0082002FD60B11229524482ADA592954 /* MJRefreshComponent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9B93846DEFDF34EC2E881F65938B3F17 /* JXSegmentedTitleOrImageItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD5BF7F25804ED6AC6B15D6801FE4AD /* JXSegmentedTitleOrImageItemModel.swift */; }; - 9C2584A9CCF8CA6C9DF2AD79DB831E70 /* IQKeyboardManager+UIKeyboardNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA467CC8E830A4407AEEA9E195B5572 /* IQKeyboardManager+UIKeyboardNotification.swift */; }; - 9C292E2C761CB97ACD00F95FA4D60E85 /* IQKeyboardManager+OrientationNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CB2CCAA127ADA834B4CCF4686ECE60 /* IQKeyboardManager+OrientationNotification.swift */; }; - 9C7D314BE45AB79E96B260656C36BAEC /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6855310DB35D41B1E466E5EFFAF771BE /* RedirectHandler.swift */; }; - 9D8BED7F3F86BB39E7C0923D92E73F8B /* Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05A41A0B8D148A4BA10DC25292FA63E6 /* Combine.swift */; }; - 9E4E278A3C1543798E1912699886023E /* IQKeyboardManager+UITextFieldViewNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07490687930290E2CF12D183E94326BF /* IQKeyboardManager+UITextFieldViewNotification.swift */; }; - 9F8F5BCE4DAD6C16F3A8AA2A2F783D01 /* FormatIndicatedCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC741E10FE2F276DD3C474545224A00 /* FormatIndicatedCacheSerializer.swift */; }; - A0371DF7D93D9B86BD50474D4B9294FD /* ConstraintInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7682AD6DBFD519C9BA5ECCAC483C0CF4 /* ConstraintInsets.swift */; }; - A051999781E1280746F7743BDEEA6C49 /* ConstraintDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5E05285408F9AA5B005A82CEFDA99 /* ConstraintDescription.swift */; }; - A1506893FF52AA466B130E8B05FBE868 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88647C8377C8FB3AE854C55C7B81559E /* NetworkReachabilityManager.swift */; }; - A1A93726CD533C8ACD4755250E46E48D /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49B5FF4D24E46BB7F695E22A9907317 /* Delegate.swift */; }; - A22A2ACF53FDC243AAAFB009005A710F /* Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B7826E0EF5E37E1704D27E7C361C78 /* Concurrency.swift */; }; - A35877DE0C3D6B4CF2A10E666EF5F490 /* LayoutConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B2E649574AA0FAECB00E9B883C49A6 /* LayoutConstraintItem.swift */; }; - A393340CB069126B0A3D781C174E6E49 /* JXSegmentedNumberItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A704FDB2D535D699FD2D53CC6DFFC70 /* JXSegmentedNumberItemModel.swift */; }; - A3FD52DF5584364FFD56965394C36CF2 /* URLConvertible+URLRequestConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD5CEA5D178799C659489E705F0BDE8 /* URLConvertible+URLRequestConvertible.swift */; }; - A50A72FCD270217D99ECA1D2700CFAD4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EFE78C092A2B391D6411B2D75E0897C5 /* PrivacyInfo.xcprivacy */; }; - A70C6EBC13661A0FC4023236B350AEA1 /* AuthenticationChallengeResponsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E5CD298A2F13E3902AAA3510FE5C9FD /* AuthenticationChallengeResponsable.swift */; }; - A7AE58053194FA1CEA420D995AF17B85 /* MJRefreshAutoStateFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B9379FA3746B5215A31DE8D1CD26166 /* MJRefreshAutoStateFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - A86A28FC358F0AFE453C2EBD1D035751 /* JXSegmentedTitleGradientDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 695AA5A7D6BF0B59DFBF71ABC3F3FECE /* JXSegmentedTitleGradientDataSource.swift */; }; - A8C0CBBC63C39A8C10083CBCA172F7CF /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07BEF0CE5DECC5BDCAC8625BF2FFA4B /* QuartzCore.framework */; }; - A8DD3F39B4D1F0C7B11866484A03336E /* IQInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCA08BA243343C7A9EAEE567CC5F4318 /* IQInvocation.swift */; }; - AA602A49B1DC7FDED565CAD8BB89EAC1 /* AFError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B98A705AFD36170CD85E492121E37403 /* AFError.swift */; }; - AA99A5B65FF84BABBAADA73755A1128E /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19D1617C2FB2FC864606303AF174A6D /* TimePeriodChain.swift */; }; - AAC2529A1B4F4832A052B348C5093018 /* IQUIScrollView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C54C3E1777728F805F3EFCB27BB703BC /* IQUIScrollView+Additions.swift */; }; - AB651DB3A97A623234B6544AD030406A /* IQNSArray+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1E1CFDA9E2D6AECCA91F210C0B6064 /* IQNSArray+Sort.swift */; }; - AC46B7DF5115A19C887EDDC9C226B66E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 84E085540B9A46FE188C3ED65522C329 /* PrivacyInfo.xcprivacy */; }; - ACDFC76B3486D1E62FD11EFAA88678ED /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B189CE2110C0A21199EB8FB87FCEB6D /* SVProgressHUD.m */; }; - AD3CE903FA2BEE3EFA153E7FA7C9610E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F86ED01EB7CC70315E585984B7CEADA2 /* PrivacyInfo.xcprivacy */; }; - B0CB5FB63262E1A67317045B8960F363 /* IQBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B860B422FB58A9842214FE11A933BBD4 /* IQBarButtonItem.swift */; }; - B133BB9C9B03481C7F1D221F35A617F2 /* Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A26DF092CAB68C5202C9271E71C8F750 /* Constraint.swift */; }; - B155E9B44BDC68C87FCC13B10F1D5532 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEA2AD00C187BA485EFF154E41EF587 /* ImageFormat.swift */; }; - B1E4796F3830DE510675DEE45E7FB648 /* JXSegmentedTitleImageDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1194040926B2C7D711B859F8AE4312 /* JXSegmentedTitleImageDataSource.swift */; }; - B2E03E67465612F21A685BC97EE7F947 /* KFImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EEB78584831D0B242BDCAA90CED58E /* KFImage.swift */; }; - B3048DDF53B358B345D8FC23A4D7D1AB /* JXSegmentedIndicatorRainbowLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DCFBED49883055AA94888206F221A9 /* JXSegmentedIndicatorRainbowLineView.swift */; }; - B3F75D115D5150C258F5E68404751010 /* ConstraintConstantTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB8A72B411C5E90471A44666337E7D2 /* ConstraintConstantTarget.swift */; }; - B46A36CA19ED6C09341D8E4031F66D5C /* IQUIViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40E3CE126BEDA57D7DA923F7BBC49D32 /* IQUIViewController+Additions.swift */; }; - B4F5A298E60CC038619FC7685C3D50C3 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E6EB2C04F247D9F4D15B77F830EDF54 /* Date+Compare.swift */; }; - B4F9C9EC1B3F1689AE56BB719AE5439C /* ConstraintDirectionalInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 570B90AE83D4F878D222EF7D7B7A18F9 /* ConstraintDirectionalInsetTarget.swift */; }; - B5C66B48EB624FEC4D2F64A50F143716 /* DataStreamRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C8C3813F3BA6EEC658167AE65D9419 /* DataStreamRequest.swift */; }; - B6E953695C48D2FDCE8D86321DE7D474 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF9BAB200D238346E29ED2DBA085B17 /* ImageCache.swift */; }; - B784C1E8FB583A4AA328D89038D2DC5B /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECC357045F3ADF02DFCA0E0F6D4DEE0 /* TimePeriod.swift */; }; - B85354283CB89C658911748A7C5AF891 /* JXSegmentedAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE736DCB96A4842E66D08021E34D36C /* JXSegmentedAnimator.swift */; }; - B962ED35C37063794B7D8468FB007AD5 /* MJRefresh-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DD688DF2ADF79B72ECC735C35F05F41 /* MJRefresh-dummy.m */; }; - B97D95F7C0C87105C015BCCAE634B8DC /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AAF19FA9EB514B2794ECCAD1BB7FA2E /* Filter.swift */; }; - B99F90601D5EED64587743374BBC44F6 /* MJRefreshStateTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = E0E418BB3B470DAB2533C31BAD7884C0 /* MJRefreshStateTrailer.m */; }; - B9A56C1C079CE609AD4404964A95A170 /* MJRefreshTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 13184921F92E5D84095DD92314A9A4EA /* MJRefreshTrailer.m */; }; - B9DCCFD9F7800B172476EE7F2D90354B /* JXSegmentedTitleAttributeDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B28820A2229C358F6F00F9A6EE1A659 /* JXSegmentedTitleAttributeDataSource.swift */; }; - BAF334D6167CAED8479849147C7F6858 /* TVMonogramView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67675B8E7AD45A266A61E66355512D15 /* TVMonogramView+Kingfisher.swift */; }; - BB84B6AC5338AF7AD3B82B1C012386F8 /* JXSegmentedDotCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2215909E83BE0517F84079C1593E4E85 /* JXSegmentedDotCell.swift */; }; - BBC9585C538DD5B68FF7E7E32F714023 /* JXSegmentedCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F1FB2699EA33ABD4EDB7F856D39296F /* JXSegmentedCollectionView.swift */; }; - BC0A0C473B63B817926F4D58611281BB /* URLEncodedFormEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E28C2D9A6137B156647AC16D95A15E /* URLEncodedFormEncoder.swift */; }; - BDAD667B60A9D9981738646851C825A0 /* IQKeyboardManager+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB34508FC3184D3482A1002BEBC16654 /* IQKeyboardManager+Toolbar.swift */; }; - BDEF755484EB2BA591FBF48752FBAEEC /* NSTextAttachment+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F69248BB24307C449A27A28477BAF56 /* NSTextAttachment+Kingfisher.swift */; }; - BE355F69F84788D334FDAC7D1CD9B8C4 /* ConstraintDirectionalInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14052CB3D5057DCF4593BD1E77D90942 /* ConstraintDirectionalInsets.swift */; }; - BE60EC19FCBB8F301081E9C31BB85F3E /* IQUITextFieldView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E8C8A30A6C29843067AEFDBDB0DD5E /* IQUITextFieldView+Additions.swift */; }; - BF567AEFDD52DC43AC75122734D571E2 /* ConstraintMakerFinalizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5545219DAAC101FE50450F293367D8C9 /* ConstraintMakerFinalizable.swift */; }; - C04DBE6AE13FC5CFD01D363A351EF76A /* ConstraintView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C19F89F8FC7DDEB64DB778A90CC2BC /* ConstraintView+Extensions.swift */; }; - C09A286120D64335EA18D7689720B773 /* NSBundle+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = B4111D05E2FD45B47E3E6AD830625F74 /* NSBundle+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7EA7AFEFA0CE4029611DA026CDBE84B2 /* id3_parser.h in Headers */ = {isa = PBXBuildFile; fileRef = D603B15A4E766FC333BB15832FCEFE56 /* id3_parser.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7F60A80731DCB0EB26C7D01DCCA932E4 /* ISOParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923221B8AC80D87BFC467045B41C9B5C /* ISOParser.swift */; }; + 80244605FFB26BF165B3C6E3CFA83A3C /* JXSegmentedTitleImageItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D810D4BC558D69052E21351222B3AF83 /* JXSegmentedTitleImageItemModel.swift */; }; + 8072251E5FFDC1AB89E7558218C41D24 /* KingfisherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A5DBCBB01B810B9E309C9CF63DD609 /* KingfisherError.swift */; }; + 81245F505360D8598E4A6C7D67E09F21 /* Double+TaskInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92F1C9075944A510D4E5A9C696355AB /* Double+TaskInfo.swift */; }; + 81DAB282980A3BB8EB7796B61934E4F4 /* KFImageOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732A9C653F736C5918C2BE6C341FA4 /* KFImageOptions.swift */; }; + 82FE3B046FEA46F2BDFE7FB0E9D7CBAD /* SVProgressHUD-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FE20AE5C1EF7F685ADC6C4B3A255B06 /* SVProgressHUD-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 830535E48AA507D7C15359F2A5B72DD1 /* ImageProgressive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E6C96CBB660219F7EF7C29135EF5D2 /* ImageProgressive.swift */; }; + 83DBC0F86AD7C6EFB6947E0F3616467E /* JXPagingListContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6382B651E5176B0926F7272EA2DE2F /* JXPagingListContainerView.swift */; }; + 8423D60239269F191A47A3E2D82E1EF7 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BFE07EB5815ED4E3061D694AFE169C5 /* ImageTransition.swift */; }; + 84BEB9E439780B1E0DEF56459E3D3352 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 8640829049AE9907FE93825E5510C33D /* ConstraintMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51197F733B8466F86DCAA59DB57532F1 /* ConstraintMaker.swift */; }; + 868D4A456CBC3255A772F7FEA2F63BE0 /* JXSegmentedIndicatorParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 760BA463607D8ACAEEA23CB221E8880E /* JXSegmentedIndicatorParams.swift */; }; + 8775BC8B188C38085646E9580CE231BA /* Kingfisher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BB2313FBFE241B878F16C54B58F931A /* Kingfisher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87996D11DC92EE19EAF9305DAEA4ACF2 /* ConstraintMakerPrioritizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F54DC04D93E64B2082AAFB9A1F4F83F /* ConstraintMakerPrioritizable.swift */; }; + 8868BA37E3CE7C58D26123806D543F3F /* ConstraintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57AAC6360170C5570DBED4913F8439E /* ConstraintView.swift */; }; + 887DB52C63E52FBD3B88F42DD8CFB421 /* RequestCompression.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E5AF2E02A2D523608B68D4601F2553 /* RequestCompression.swift */; }; + 891D1BF14C8881C74262EE9DBE5D67A9 /* UILayoutSupport+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E0088C30E4A370DC59A6329299BF5F /* UILayoutSupport+Extensions.swift */; }; + 892E73423E4F812F4DBF43F2BEC21838 /* FSPlaylistItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 8200191799409C278154BB17FF45D90E /* FSPlaylistItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8C077A05101B84731A302657381218CB /* MJRefreshStateHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 9218CCDDFFCB0E70020117C647E18758 /* MJRefreshStateHeader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8CEABAE06B171EA941EB497A2F4A6917 /* Runtime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F386566B21015AF7730984275FC385 /* Runtime.swift */; }; + 8D18198290C6F15504358DD49F0C505F /* JXSegmentedTitleOrImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15D0636F86B144901B1E79D45A5EF72 /* JXSegmentedTitleOrImageCell.swift */; }; + 8DD0EA5259D87AA915FC266D43CD08D6 /* JXSegmentedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D894D9E7403FED9BC0BC7102A3429 /* JXSegmentedView.swift */; }; + 8DD46EE7FB9503E7634E929DDE1CBA31 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF56CAE407A1246D8E33A86EEAB1CD0 /* Notifications.swift */; }; + 90441CBC43993A5E4E0F5CD5BECC2DDE /* MJRefreshBackNormalFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A21DE84B8BBD51EE7C86AF1310AB8DB /* MJRefreshBackNormalFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 906EF891E58A035281766993F82373E3 /* ConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEB7265F1F1E1E6447ED57D564548109 /* ConstraintItem.swift */; }; + 90D847B19214926EDE5210D44A08F3C7 /* RequestTaskMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A9C6C311E69800A3DBB32A5CE9A829 /* RequestTaskMap.swift */; }; + 9136355471B1C08A16DA36B0A2E536B8 /* NSButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD850529AEAAF1F52C7286B5088D828B /* NSButton+Kingfisher.swift */; }; + 917BE8C320E784763B6235AFF4751F0A /* file_output.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6473EC965BEEC93B5FEB22E0B16F7E19 /* file_output.cpp */; }; + 9189CD495B78CBC65B25DED32A881426 /* KFOptionsSetter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEBA9C1E1A64D0D2E56C9ECA1009355 /* KFOptionsSetter.swift */; }; + 91DE350C08092882D3AD0384A4213398 /* stream_configuration.h in Headers */ = {isa = PBXBuildFile; fileRef = 69A60B500F2A28EF683EAB65A6568396 /* stream_configuration.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 91E3A6B42A7C2B5B57A4C3AD62DECDD4 /* audio_stream.h in Headers */ = {isa = PBXBuildFile; fileRef = 6852392DB22126B984A79E66779323DD /* audio_stream.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 92138A77DFEB4F76FCB582E97633896D /* URLRequest+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D50102F684B52D0EC7CEDB9B357C54E /* URLRequest+Alamofire.swift */; }; + 9255A2BCECEB0006DAAF985CA00E2D15 /* JXSegmentedTitleGradientItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D9D13FC61F185D3F12CC9AB311971C /* JXSegmentedTitleGradientItemModel.swift */; }; + 92A81311D8592CB3D907E9DA59023E38 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 203B8403E6E3D7A6813164E42EA97906 /* PrivacyInfo.xcprivacy */; }; + 930B495B7A3197730A016E2339A4CBA7 /* IQKeyboardManagerSwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EBF85EEDF18C69141A9E1E703C905CD /* IQKeyboardManagerSwift-dummy.m */; }; + 932470342D7352EEC18C00E52B37A5AE /* DotNetParserFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881A67EADDDFC811B6A54CC57595D3F8 /* DotNetParserFormatter.swift */; }; + 9378157945D7B405C862A05B0D6B971B /* Result+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873505EB51B12F5BA6246196F84D915B /* Result+Alamofire.swift */; }; + 938BE9A0AD26831EFD7B31DD37ADB045 /* Reachability-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CEA9547D3BA26D287FEB73F3765D4196 /* Reachability-dummy.m */; }; + 938CFF5F6DE6E7E8CE94373E51F3E07A /* ConstraintLayoutGuide+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56F310A7F250FAC36DFA8E59F573C957 /* ConstraintLayoutGuide+Extensions.swift */; }; + 94E00674FACB3ED40684E8DAA40BCC84 /* KF.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A2F06B87F663A92FCC6AFF0B47689F /* KF.swift */; }; + 950B809760A8CE4375DCE1016FB9859F /* ConstraintMakerRelatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2618F09609D8CBB18F768D2EE4A14162 /* ConstraintMakerRelatable.swift */; }; + 9571E1CF11BE724718E87ECBB448A146 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63D0F1F07C36562816CB8E6D41FBDB3E /* SystemConfiguration.framework */; }; + 9687320638B5AF16C99AF038C957BB76 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 9779D52AD8CDA703D6EEE1C6D38E019F /* ConstraintAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DD4FB14C7316C4A190EC2E458275B19 /* ConstraintAttributes.swift */; }; + 978653D1A915C487464FCB9DB451C824 /* file_stream.h in Headers */ = {isa = PBXBuildFile; fileRef = B4E3529EFC2A1018DD6CBC38DC725AFF /* file_stream.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 97D7D91FC818805D8344C373CC098C32 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D97EB1D469B927CC01C4FEE81A6AB26 /* SessionDelegate.swift */; }; + 97EA46DA072EC4FD4D831606095B92C1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB406B5AB28CD8F5747EBE9498A2F869 /* UIKit.framework */; }; + 98455F4176C861F9E33D36892A932684 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + 995C9FC5FD11DAE810CF708F3FAFB94A /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 255E5C87081214F2B68D64E82AA9DD61 /* PrivacyInfo.xcprivacy */; }; + 99F2413E51A57A17AF0B52EE43D448D3 /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AA35E31BC370E094419B4110DEBB61 /* Resource.swift */; }; + 99FB2910921988A9BCC5533A1EA70E59 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CC9FECC112EC531F730A6997A7AA46 /* Region.swift */; }; + 9A07F0B734748735A80119550AC32104 /* SVIndefiniteAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F496457565E077ADD0E21CF6E432F08 /* SVIndefiniteAnimatedView.m */; }; + 9AF14E4B1C52E5AD4C38021C4EC17974 /* UIScrollView+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 2952FA317462D1808CC73087B4EC0DA8 /* UIScrollView+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9AF53260DACD3DE030C97AA110DAD22E /* MJRefreshComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 882F5DAB4B06E95F1A5C4237CE16D896 /* MJRefreshComponent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9B93846DEFDF34EC2E881F65938B3F17 /* JXSegmentedTitleOrImageItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ED521E4CBFEA0F5D9E01C3CCE5307D5 /* JXSegmentedTitleOrImageItemModel.swift */; }; + 9C2584A9CCF8CA6C9DF2AD79DB831E70 /* IQKeyboardManager+UIKeyboardNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7890939418A54F31B5E2A8545372FD /* IQKeyboardManager+UIKeyboardNotification.swift */; }; + 9C292E2C761CB97ACD00F95FA4D60E85 /* IQKeyboardManager+OrientationNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5097DCB7A30413A98441BDB44CF3B14 /* IQKeyboardManager+OrientationNotification.swift */; }; + 9C7D314BE45AB79E96B260656C36BAEC /* RedirectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E6D81A1886132FB59F96D28A386AB9 /* RedirectHandler.swift */; }; + 9D8BED7F3F86BB39E7C0923D92E73F8B /* Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73263B2692F575379BC2971651456D08 /* Combine.swift */; }; + 9DE54A6FA3EA23631ADD6DEC4D190EC9 /* String+Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963052BB2E62093DA5CBB64F34A6F258 /* String+Hash.swift */; }; + 9E4E278A3C1543798E1912699886023E /* IQKeyboardManager+UITextFieldViewNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB2F1CC639A1BFB895317FCBAF22C6CB /* IQKeyboardManager+UITextFieldViewNotification.swift */; }; + 9F8F5BCE4DAD6C16F3A8AA2A2F783D01 /* FormatIndicatedCacheSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0053FAA6FA0A2E35B78413CFFFE3D7DF /* FormatIndicatedCacheSerializer.swift */; }; + A0371DF7D93D9B86BD50474D4B9294FD /* ConstraintInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B11D4A2DB281C9D6FC1C296D5E1AA3F9 /* ConstraintInsets.swift */; }; + A051999781E1280746F7743BDEEA6C49 /* ConstraintDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = F98723A48A6B9BA3B70185B530DBD6C9 /* ConstraintDescription.swift */; }; + A1506893FF52AA466B130E8B05FBE868 /* NetworkReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F835F11137C679D5CF9597B87B42CEC /* NetworkReachabilityManager.swift */; }; + A1A93726CD533C8ACD4755250E46E48D /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B2754B6551345D199982248BF5F258 /* Delegate.swift */; }; + A22A2ACF53FDC243AAAFB009005A710F /* Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C54ECE18F0497523A08897E77463C71 /* Concurrency.swift */; }; + A35877DE0C3D6B4CF2A10E666EF5F490 /* LayoutConstraintItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55061A053D3068F4AD9F6B8884C86A1B /* LayoutConstraintItem.swift */; }; + A393340CB069126B0A3D781C174E6E49 /* JXSegmentedNumberItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A63046EDC8D9C3318D3B5C0B3193AB /* JXSegmentedNumberItemModel.swift */; }; + A3FD52DF5584364FFD56965394C36CF2 /* URLConvertible+URLRequestConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186CD6B1C19F3A72817C08755AF2F5A5 /* URLConvertible+URLRequestConvertible.swift */; }; + A50A72FCD270217D99ECA1D2700CFAD4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E05B0E4ECA120B98E41CFF3E9BAF9A59 /* PrivacyInfo.xcprivacy */; }; + A70C6EBC13661A0FC4023236B350AEA1 /* AuthenticationChallengeResponsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1318FA20C5E765D28786F3F849CDB26E /* AuthenticationChallengeResponsable.swift */; }; + A7AE58053194FA1CEA420D995AF17B85 /* MJRefreshAutoStateFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = EC85421228E0616F4EDD11D8427A8E09 /* MJRefreshAutoStateFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A86A28FC358F0AFE453C2EBD1D035751 /* JXSegmentedTitleGradientDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9929DB3FD7ABBE4F7AD3D7BA8F4ED4D /* JXSegmentedTitleGradientDataSource.swift */; }; + A8C0CBBC63C39A8C10083CBCA172F7CF /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1EF6CE4DCA3A7EB57EF30D95912E1CC /* QuartzCore.framework */; }; + A8DD3F39B4D1F0C7B11866484A03336E /* IQInvocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2E016699F6BADA8EE18EC03EEAF1CC8 /* IQInvocation.swift */; }; + AA602A49B1DC7FDED565CAD8BB89EAC1 /* AFError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951109FA965A9BDC4FC4E338EE37DE65 /* AFError.swift */; }; + AA99A5B65FF84BABBAADA73755A1128E /* TimePeriodChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 762E686A23E3C05BFD37C98D4CBA9424 /* TimePeriodChain.swift */; }; + AAC2529A1B4F4832A052B348C5093018 /* IQUIScrollView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB4351FC3305AC9C5E1A62A27449DDD5 /* IQUIScrollView+Additions.swift */; }; + AB651DB3A97A623234B6544AD030406A /* IQNSArray+Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A8CE17F7BEFB6AF765222B453E745F0 /* IQNSArray+Sort.swift */; }; + ACDFC76B3486D1E62FD11EFAA88678ED /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A7AE61E79272DD6D46024C339DD62FE /* SVProgressHUD.m */; }; + AD494A6CD3724BE63AA1BDDA1196742B /* FSCheckContentTypeRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FCD33E634B250907AA661A3C5F2C19E /* FSCheckContentTypeRequest.m */; }; + AD4F228D11A3B45BE1024B59DEA210AA /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 54BC69F7BD5CCB745F0FB103B3478E38 /* PrivacyInfo.xcprivacy */; }; + AD6C25D10A1AA207FDB850E5F9A55758 /* FSParsePlaylistRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = EC2449762D441F345F1C4B12A439D080 /* FSParsePlaylistRequest.m */; }; + AE297D4EC616170B7A43E28DD30D7556 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + AEF9D1355E1DECBEF39B652B703FAC40 /* FSXMLHttpRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = FEFADF03AF0101330230B2A77AA48B0B /* FSXMLHttpRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B0CB5FB63262E1A67317045B8960F363 /* IQBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8226608B94B7947A425E8D90B33C1E /* IQBarButtonItem.swift */; }; + B133BB9C9B03481C7F1D221F35A617F2 /* Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8447CAFBD6F721656250A2409FCA240 /* Constraint.swift */; }; + B14FAF976D4BFA1065F209FC49DB722E /* Tiercel-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = F5B640A3A5A56570BD6D1B5693A7C4D8 /* Tiercel-dummy.m */; }; + B155E9B44BDC68C87FCC13B10F1D5532 /* ImageFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1880EFAED23577D0DE35F1DF62E176 /* ImageFormat.swift */; }; + B1E4796F3830DE510675DEE45E7FB648 /* JXSegmentedTitleImageDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3E2B8B92BC8BB5E3EEF4A78D2464797 /* JXSegmentedTitleImageDataSource.swift */; }; + B2E03E67465612F21A685BC97EE7F947 /* KFImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416332D3DF3451F4A442AC1248F5BEB2 /* KFImage.swift */; }; + B3048DDF53B358B345D8FC23A4D7D1AB /* JXSegmentedIndicatorRainbowLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A946002663B961D8830937B5D6C506 /* JXSegmentedIndicatorRainbowLineView.swift */; }; + B375F549CB165674909966DC5BE9C1F8 /* FSParseRssPodcastFeedRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = C5BC05F5D88A6640656E78CCE8D5018D /* FSParseRssPodcastFeedRequest.m */; }; + B3F75D115D5150C258F5E68404751010 /* ConstraintConstantTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B502A2849402E0A52A4D175507A77 /* ConstraintConstantTarget.swift */; }; + B46A36CA19ED6C09341D8E4031F66D5C /* IQUIViewController+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00F9A44E0AE14DC8B34030B2DA760B3 /* IQUIViewController+Additions.swift */; }; + B4BD80C0A29183CA9B1BDB5D8A73545C /* audio_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91375183D8F9D59BCF0B45DD272B7E1D /* audio_stream.cpp */; }; + B4F5A298E60CC038619FC7685C3D50C3 /* Date+Compare.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6F1C5A009876A142269C61CC4EC719C /* Date+Compare.swift */; }; + B4F9C9EC1B3F1689AE56BB719AE5439C /* ConstraintDirectionalInsetTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD9608BE5DC94C79C3B80889048CC2C9 /* ConstraintDirectionalInsetTarget.swift */; }; + B5C66B48EB624FEC4D2F64A50F143716 /* DataStreamRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FCD1CF754595FC1050F9E2BE4EE7FE /* DataStreamRequest.swift */; }; + B6E953695C48D2FDCE8D86321DE7D474 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B283FDE7FACB318CC1398FECAB76E7 /* ImageCache.swift */; }; + B784C1E8FB583A4AA328D89038D2DC5B /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9A86C2DA499569E7EB235FF55769B3 /* TimePeriod.swift */; }; + B85354283CB89C658911748A7C5AF891 /* JXSegmentedAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38D414568B081DC66E575280B0C9F52A /* JXSegmentedAnimator.swift */; }; + B962ED35C37063794B7D8468FB007AD5 /* MJRefresh-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 979BC6537C00870AA3E990B2726CA8B8 /* MJRefresh-dummy.m */; }; + B97D95F7C0C87105C015BCCAE634B8DC /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA4BDFF82A93332CC58469234702EB5 /* Filter.swift */; }; + B99F90601D5EED64587743374BBC44F6 /* MJRefreshStateTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 68F6612E77D997F33CF6B5B66DC48B2D /* MJRefreshStateTrailer.m */; }; + B9A56C1C079CE609AD4404964A95A170 /* MJRefreshTrailer.m in Sources */ = {isa = PBXBuildFile; fileRef = 97FF07A23DCF1ECAAE9FF2E505D9B5C9 /* MJRefreshTrailer.m */; }; + B9DCCFD9F7800B172476EE7F2D90354B /* JXSegmentedTitleAttributeDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F0C37A6C30DB306561499EA4E6BCDC /* JXSegmentedTitleAttributeDataSource.swift */; }; + BAF334D6167CAED8479849147C7F6858 /* TVMonogramView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6E75DD4281F6901C196B196AC70734 /* TVMonogramView+Kingfisher.swift */; }; + BB84B6AC5338AF7AD3B82B1C012386F8 /* JXSegmentedDotCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7C6DA07E0232526BB718A5A28B5C3F /* JXSegmentedDotCell.swift */; }; + BBC9585C538DD5B68FF7E7E32F714023 /* JXSegmentedCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07665A785548E8E674CB0127D9EED147 /* JXSegmentedCollectionView.swift */; }; + BC0A0C473B63B817926F4D58611281BB /* URLEncodedFormEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 151DB2B13390607CE0EBC6E10AFD1E04 /* URLEncodedFormEncoder.swift */; }; + BDAD667B60A9D9981738646851C825A0 /* IQKeyboardManager+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0C913B6259067973CC83E161BC902F /* IQKeyboardManager+Toolbar.swift */; }; + BDEF755484EB2BA591FBF48752FBAEEC /* NSTextAttachment+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD37EFEDECC05ECBBA0B449990EF7C0F /* NSTextAttachment+Kingfisher.swift */; }; + BE355F69F84788D334FDAC7D1CD9B8C4 /* ConstraintDirectionalInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CDB522A56DDB4AA1E1324E6A24C7710 /* ConstraintDirectionalInsets.swift */; }; + BE60EC19FCBB8F301081E9C31BB85F3E /* IQUITextFieldView+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69708C606EE0513511D8D46B93793A9D /* IQUITextFieldView+Additions.swift */; }; + BF567AEFDD52DC43AC75122734D571E2 /* ConstraintMakerFinalizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4DBCFFFB504E9F7837D6E205CD534A /* ConstraintMakerFinalizable.swift */; }; + C04DBE6AE13FC5CFD01D363A351EF76A /* ConstraintView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13CD38CD80EF3B99DB3C7067477E8CB9 /* ConstraintView+Extensions.swift */; }; + C09A286120D64335EA18D7689720B773 /* NSBundle+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = C01E50B447FBD15488751F2415EB6952 /* NSBundle+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C439F42FE025FC9A6582455995D82001 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = E7535AABBE9FF3A343D71B790DF34BE4 /* Reachability.m */; }; + C58DB71C6C298B2F2144AE20D9679995 /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BC635388215C143B7967569B49BCBA1 /* Reachability.h */; settings = {ATTRIBUTES = (Public, ); }; }; C5C4137EA46E97E717E83EF0ACEBC695 /* JXSegmentedView-JXSegmentedView in Resources */ = {isa = PBXBuildFile; fileRef = 92B0EC788EDA1B0CFA48DFFCB3DDAECD /* JXSegmentedView-JXSegmentedView */; }; - C6675F7517783A748EEF6AF441B187EB /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D65D8CBF9D74AB85CB305B6D9B87509 /* DiskStorage.swift */; }; - C7E343559158D03F717C616F79FAA006 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - C97C4259FD08F4E7519F858AA06B1A3B /* JXSegmentedRTLLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB0205A7B7089CE3AA7AF45ACDE3629A /* JXSegmentedRTLLayout.swift */; }; - C9D65759B4F36BDD6F29F0D4EA18AEFF /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = A206F578A0BA9FC1C6B8B6078EFF6B9A /* SVProgressHUD.bundle */; }; - CA2820BE946964DCBC8E68604B34FD10 /* SizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C0F797B07A4C95A5F90FB6C7E6A296 /* SizeExtensions.swift */; }; + C6675F7517783A748EEF6AF441B187EB /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA1B564614FA5892379C63D94F871C4 /* DiskStorage.swift */; }; + C7484979F5A458C2BCBA24D30AB975F6 /* FreeStreamer-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = AECF3DE66EAD86C69CFB89D6FDEE4268 /* FreeStreamer-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C7A89997D6851D13CA43ECC1E7F60F90 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = DA2A9ABE6AB044A8A75E993DD7A7D429 /* PrivacyInfo.xcprivacy */; }; + C7E343559158D03F717C616F79FAA006 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + C97C4259FD08F4E7519F858AA06B1A3B /* JXSegmentedRTLLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 975850A5F9F51047F413323BDD9F682E /* JXSegmentedRTLLayout.swift */; }; + C9D65759B4F36BDD6F29F0D4EA18AEFF /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 9D2626745F360CF15E27E55F46760135 /* SVProgressHUD.bundle */; }; + CA2820BE946964DCBC8E68604B34FD10 /* SizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C48D497CEEA7EB34E2FC6194741A4F4 /* SizeExtensions.swift */; }; CCD6784611A32AE82D759F673AD23B47 /* JXPagingView-JXPagingView in Resources */ = {isa = PBXBuildFile; fileRef = 7EB20B4E68CCB69F85E7D08B7F8463D6 /* JXPagingView-JXPagingView */; }; - CDCA01B605A086576DBB75F8C3A24337 /* RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10D7C8D9A8EFE21347CF640AE5AFCD9 /* RetryPolicy.swift */; }; - CE453C62D0DA474AEE306AF3DF6DDAF1 /* MJRefreshConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 813F5F884BA0F0F6C062E545B4CA81EC /* MJRefreshConfig.m */; }; - D06D49E07A6416A2A6E41B7B7B0D66F2 /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36730D770821B0E94A45B2817FC4E2FD /* ImagePrefetcher.swift */; }; + CDCA01B605A086576DBB75F8C3A24337 /* RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1C920032B029FBF2CB4D3E422174E9 /* RetryPolicy.swift */; }; + CDD01A2CBF13747972C51B1AE7A10B31 /* DispatchQueue+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B2767297008EECD4B485DFC38F2111 /* DispatchQueue+Safe.swift */; }; + CE453C62D0DA474AEE306AF3DF6DDAF1 /* MJRefreshConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = EDD206CF9334FE1FE5774C8F2948BF37 /* MJRefreshConfig.m */; }; + CFDC85864B426F19A908AD3E8F795D53 /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2EF2CB55AE8534BFE107E6DC7FFC4D /* Protected.swift */; }; + D06D49E07A6416A2A6E41B7B7B0D66F2 /* ImagePrefetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31511FD256433B92C0FEEE45809A116 /* ImagePrefetcher.swift */; }; D091D9EA20CEB92609BF1E622E8CA348 /* SnapKit-SnapKit_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = B9DCB5EC0B1CDADD221717CADDF62359 /* SnapKit-SnapKit_Privacy */; }; - D0DF994786BCEC54939BC8216B42FBC4 /* IQTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16F379BC5CA8C2B21212962C6FA430 /* IQTextView.swift */; }; - D191F3F5F0841B63F1F54A430608830E /* MJRefreshFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = C76A1ABBD38622E820640903E4A979AD /* MJRefreshFooter.m */; }; - D219C90C04F199356B9E9356693A3D59 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0422DB62B6B3C1A201A2762EBBB5A97 /* Date.swift */; }; - D21E59C63A36A71A0B639350BA49E1A5 /* JXSegmentedView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 37C1098A010D1C8E4E069D976A8D8DFD /* JXSegmentedView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D313FF15F05623E5026892D032C08A30 /* JXSegmentedIndicatorLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F125D521A9DA47CB8D38C8C03AEF68A0 /* JXSegmentedIndicatorLineView.swift */; }; - D32F246A21567182179C39B12D534191 /* MJRefreshBackGifFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F08C4884167A8DF12652D4ADA87DC8E /* MJRefreshBackGifFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D46A095C95DE691323E1D470F9DA1A39 /* KingfisherOptionsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47365FDDB9CB0AC423AD6C3AF49AD21 /* KingfisherOptionsInfo.swift */; }; - D496B7637BE491EE925D965EC64A46E8 /* MJRefreshBackStateFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 67853DE27314756744C79CE55FBAA9A4 /* MJRefreshBackStateFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D4A9338A969C1416E3C79CECCB97D514 /* SVIndefiniteAnimatedView.h in Headers */ = {isa = PBXBuildFile; fileRef = 282ACC9FB46C251345FC5EB3AEF1B9CE /* SVIndefiniteAnimatedView.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D4C21761CC592857F35B99ACDD62ADAB /* MJRefreshAutoNormalFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = A04C1CC2D7176FD20CE057E555EFC2CB /* MJRefreshAutoNormalFooter.m */; }; - D4E2EAD773A30B252B6AD6B99A7490F4 /* IQKeyboardManagerSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 98D0F4FF0A3E2660BE566528FDF31D03 /* IQKeyboardManagerSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D5CDB942C402656138596C179E5A64EB /* ImageModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D776B0C4D357F0B0466B42C8BF6495D5 /* ImageModifier.swift */; }; - D6A1AB77D9F4C84779521FAF5E469343 /* MJRefreshAutoFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A43EE3F113EE7EB81E5065DB5FDD3F2 /* MJRefreshAutoFooter.m */; }; - D6C6BF7DF334ECB734DF0F1AC176184B /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1F7BD2E4AFF6989C1387F2558185ED /* SessionDelegate.swift */; }; - D6D4AB4590700B3706919889BF614D26 /* SVRadialGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5B1F68CF04B5EBB405EA5A612234B6 /* SVRadialGradientLayer.m */; }; - D786D7CA6B8CE037FE8376A1DD390506 /* JXSegmentedBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA08845BC3434B44AF0A3C8AFC111D93 /* JXSegmentedBaseCell.swift */; }; - D92AEF78B87F929D88D5C876ABBAD79A /* ConstraintLayoutSupportDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ADEBF08B3C1426137C7A2ED9E5E54D8 /* ConstraintLayoutSupportDSL.swift */; }; - DAA39F07C362D3F2DB232609D93CDF6B /* ImageDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D053BD683C26F73E7D5BAADC8A4FEF /* ImageDrawing.swift */; }; - DE896085DFDD686BDBDEFB776F0D683A /* DispatchQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11AF900AC1C5B4CD4047B709266568E5 /* DispatchQueue+Alamofire.swift */; }; - DE8F5B68839128A005EE3549A1149B09 /* RequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89E20D36B74F4A890688A090C311698 /* RequestInterceptor.swift */; }; - E06C5FBFE7D88C3630BA1FD51F7AF1E7 /* SVProgressHUD-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EC583B42A01E6257C74C1DCD53164A8 /* SVProgressHUD-dummy.m */; }; - E1C7B6DB080AC2293002CC3C12B136AD /* UIButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66CFC994F45F4A9A50C54C16724D763 /* UIButton+Kingfisher.swift */; }; - E2112B22B6B162AE91C23934B3F5C481 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A219042E44DB63D69EE3B79763130C /* Source.swift */; }; - E55E592D62CAE3D0B2960146E3D15CC6 /* UIView+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = E3C11DA33AB8B0E9203520516067EC23 /* UIView+MJExtension.m */; }; - E598C1E3C7A57F92401D607E5675ADD3 /* JXSegmentedIndicatorDotLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0CE74EB3DE207E3CAE98CDE17280DBA /* JXSegmentedIndicatorDotLineView.swift */; }; - E6ED06AC318A34F7744B32CEC759CDA9 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1383471ECB9F37892C9CC3EF67CA0375 /* Response.swift */; }; - E702D99CA55B52C306544C4DDF9083DA /* MJRefreshBackGifFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 50DD20A7091C1AF20BBDAE8FF40C638E /* MJRefreshBackGifFooter.m */; }; - E718E500074E8D6B011D53571B27BC0D /* Kingfisher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 33033614BAFA0628A44D6CE5D2EDBB09 /* Kingfisher-dummy.m */; }; - E818AF38E910251104A9A8AFC9227C3C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - E8AD52B68BDE7B679B358601CCAB3F2D /* UICollectionViewLayout+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = A418AA4ADA9598EFD690FF4427F182A5 /* UICollectionViewLayout+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E94B31DCAF75D93405D3BAE188604EB2 /* ConstraintConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837DA8FB2DF8148CEE1EA57A72EE570 /* ConstraintConfig.swift */; }; - EB126B698D642942058D9B676EC1E32B /* MJRefreshConst.m in Sources */ = {isa = PBXBuildFile; fileRef = 27798DCE9459ACACF37012755F087DB9 /* MJRefreshConst.m */; }; - ECAA15FA3C4560E3287F2226EC8C1ECF /* IQKeyboardManagerConstantsInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398152B547C8B96AD8C376F10BEB5701 /* IQKeyboardManagerConstantsInternal.swift */; }; - ED3AF010A8D34BC50D7C0D7BE3D2E890 /* JXPagingView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 04446B3B53853AB15AE3B7B85CF9343D /* JXPagingView-dummy.m */; }; - EF9B92EF4A0412D775FA55E4D1CB7A45 /* MJRefreshConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = BB17A1319D51982E2F7A99EFC3D11F38 /* MJRefreshConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EFC90283F9AB43BB6FF377812BD3673F /* ConstraintPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = A78C6D5D45F26A283595741E6566040D /* ConstraintPriority.swift */; }; - F00E48AB2D923607D9B91DC61DBDEB8F /* MJRefreshAutoGifFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = A10B7F476AA2BC85DA32CDCA695E94CF /* MJRefreshAutoGifFooter.m */; }; - F04A9832815B9EF600EF7387C6882D3D /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C554CE5C82E098DD3FF24BA75EE7490 /* DateInRegion+Math.swift */; }; - F0AB498412A415474D6105F398614F74 /* JXSegmentedComponetGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E3BC01C35A4242976E6B95431C9F17 /* JXSegmentedComponetGradientView.swift */; }; - F0DF27608B184B348B3127233761F4BA /* ExtensionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCB35EFBB8A545F2F8352A7D27D5B2A /* ExtensionHelpers.swift */; }; - F2E9C9068E8434E9FC9B60755A354FA8 /* MJRefreshBackNormalFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8F4284CBAD963633A64783E4CFB37D /* MJRefreshBackNormalFooter.m */; }; - F366D04DEA0EBBCA9CA4F1F4E29695E8 /* ConstraintDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92A995C2CF52A57109B1F08F9E6044 /* ConstraintDSL.swift */; }; - F3B5353C1CDE6C2DD2E80F32D3637750 /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61585200C65A159F5B6731050D64BA59 /* SessionDataTask.swift */; }; - F3EC0ED4BED8DD2C3222C7DCAC2452D2 /* JXSegmentedTitleAttributeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95438C214C29A3893A1B32DE8B1898F /* JXSegmentedTitleAttributeCell.swift */; }; - F404BFA0E5F2CFF051688C90B319AC85 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8138B9E4A2A6AA8C683562DF69844549 /* AnimatedImageView.swift */; }; - F47587932A67D8E3820DAFD9A0E1995E /* IQPlaceholderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC2D820ED623D3C956582179E970607 /* IQPlaceholderable.swift */; }; - F4DD0AD58DDD5641BDEAEA6CF44FF0ED /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB30FE6997717CE3E7589C60A9C7A9E /* Protected.swift */; }; - F69925E3D8812AAFC099940721D12AEC /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2752DAD78FD744FDF40B54ADB0708E81 /* Locales.swift */; }; - F7E576E007A81E0EFD2E0849CB17878D /* WebSocketRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A793885CE536998701B9632A60B45781 /* WebSocketRequest.swift */; }; - F94703ED86C58DDBE5A2503D148CD040 /* ConstraintRelation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7477826A60436064D61A5EFA564B9D6F /* ConstraintRelation.swift */; }; - FA1E35E6DE6EC8A9E5E2B12A414E1B70 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2A128F3FAE37601751167FDC47182A /* CFNetwork.framework */; }; - FB2EEA5AF5278F76CD7366B23CC66815 /* ImageView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A296F41C13699333333D3C630E92F80 /* ImageView+Kingfisher.swift */; }; - FC524E181A75784881A12562BDB00CC6 /* MJRefreshAutoGifFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 1624F448C0756E91DC913FD781A9A9EC /* MJRefreshAutoGifFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FC745954D9A2704BCACCB3A03336976E /* UIScrollView+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B4DD5E3C3843A80F1882655B380DCDA /* UIScrollView+MJExtension.m */; }; - FCE62086E1AB54A4F61EBCDBA15C1510 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393CAB07C81E1F3ACA4DDB1CAB615E1B /* EventMonitor.swift */; }; - FCEB3C1A7FCD1518AF8985822E2E14AA /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662BA990AF2EEE7892AF8EA530C1AE31 /* ImageProcessor.swift */; }; - FDEB8D2D369F9FEBF8FC3D822E5C96C6 /* JXPagingMainTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB420F181A7222985A450867020EFB2C /* JXPagingMainTableView.swift */; }; - FE8F4A5C40BA40C835CA301C92AED5E0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */; }; - FEC85148AFF458241FDD707C5BA8CD40 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 725764D6EDA104D9902913F6D02AB7FF /* TimePeriod+Support.swift */; }; - FEDB5503231B230FDA7C7A25EAB38318 /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF512DBF4BC4483F9F306E2B3A5CDB70 /* DateComponents+Extras.swift */; }; - FF886124915FEF2A6FBB663CA621B4FC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C07BEF0CE5DECC5BDCAC8625BF2FFA4B /* QuartzCore.framework */; }; - FFD7E1B8FA0F3960BE24DA2D20647332 /* ConstraintRelatableTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C483D88FC9B266C71FB54AA488C58BBD /* ConstraintRelatableTarget.swift */; }; + D0DF994786BCEC54939BC8216B42FBC4 /* IQTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8316A6A2618957E57AB5B31051B3AC /* IQTextView.swift */; }; + D191F3F5F0841B63F1F54A430608830E /* MJRefreshFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5095BA006903F202D549CE2B34B14AA2 /* MJRefreshFooter.m */; }; + D219C90C04F199356B9E9356693A3D59 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08285191D7204A02272F9EB631D02466 /* Date.swift */; }; + D21E59C63A36A71A0B639350BA49E1A5 /* JXSegmentedView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F341F9F0357ED1D61BA9EB641A433B /* JXSegmentedView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D313FF15F05623E5026892D032C08A30 /* JXSegmentedIndicatorLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC89394EB57F40E775111D40AA7DB95 /* JXSegmentedIndicatorLineView.swift */; }; + D32F246A21567182179C39B12D534191 /* MJRefreshBackGifFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 53FD870D570E7836D8D696AFA899C174 /* MJRefreshBackGifFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D46A095C95DE691323E1D470F9DA1A39 /* KingfisherOptionsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B75C82B4FA45FBD610EE3820F36ABA /* KingfisherOptionsInfo.swift */; }; + D496B7637BE491EE925D965EC64A46E8 /* MJRefreshBackStateFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = F2D1BE90941DDDB05B7D26E18A88B5C8 /* MJRefreshBackStateFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D4A9338A969C1416E3C79CECCB97D514 /* SVIndefiniteAnimatedView.h in Headers */ = {isa = PBXBuildFile; fileRef = BAC58F8D0F7FE424A8AD14D75A30EED3 /* SVIndefiniteAnimatedView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D4C21761CC592857F35B99ACDD62ADAB /* MJRefreshAutoNormalFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = C9F3AF20D10C3D67C0A9CBA28E59360D /* MJRefreshAutoNormalFooter.m */; }; + D4E2EAD773A30B252B6AD6B99A7490F4 /* IQKeyboardManagerSwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 19AD0AEC321066C273D5EC90BC2DF7A9 /* IQKeyboardManagerSwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D5CDB942C402656138596C179E5A64EB /* ImageModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87FE7DB732EAE613423384F1B5F8C6F1 /* ImageModifier.swift */; }; + D6A1AB77D9F4C84779521FAF5E469343 /* MJRefreshAutoFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 803E8B9AADE51E96A38692668BA81BEF /* MJRefreshAutoFooter.m */; }; + D6C6BF7DF334ECB734DF0F1AC176184B /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BFAD2523C290A22C7977CF6F4A6936 /* SessionDelegate.swift */; }; + D6D4AB4590700B3706919889BF614D26 /* SVRadialGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = E5F270A035F8B56FC159D8CF94EAAEE3 /* SVRadialGradientLayer.m */; }; + D786D7CA6B8CE037FE8376A1DD390506 /* JXSegmentedBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192580DED59C4D3B135E94F830FCA5B3 /* JXSegmentedBaseCell.swift */; }; + D92AEF78B87F929D88D5C876ABBAD79A /* ConstraintLayoutSupportDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26FEF928162FDEE5CFCE3EF99770572 /* ConstraintLayoutSupportDSL.swift */; }; + DAA39F07C362D3F2DB232609D93CDF6B /* ImageDrawing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5140A9A1F5487CA7CF613C7220D594D3 /* ImageDrawing.swift */; }; + DC5585CD93EAB3E448D3DD3E27576173 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + DE896085DFDD686BDBDEFB776F0D683A /* DispatchQueue+Alamofire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085EBD55C44E75117740E1AA43932349 /* DispatchQueue+Alamofire.swift */; }; + DE8F5B68839128A005EE3549A1149B09 /* RequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532AAD5D5F6498A0595431E5809B9229 /* RequestInterceptor.swift */; }; + E06C5FBFE7D88C3630BA1FD51F7AF1E7 /* SVProgressHUD-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 322678B5A6885D82212954266BD74117 /* SVProgressHUD-dummy.m */; }; + E1C7B6DB080AC2293002CC3C12B136AD /* UIButton+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB824843A0F095DB5E82D895A8407A4F /* UIButton+Kingfisher.swift */; }; + E2112B22B6B162AE91C23934B3F5C481 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC0854991635391659B1DA8A0A98CC9 /* Source.swift */; }; + E2BB8FD3C0A59A4D81911A2A70CC74C7 /* CodingUserInfoKey+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E3E285614C475158E65C94AA0A728CD /* CodingUserInfoKey+Cache.swift */; }; + E4C41EFC9A8AADF64F5D08D670E2CDF4 /* FSCheckContentTypeRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 90391DB72A0C3217ED3F84491E44D38F /* FSCheckContentTypeRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E55E592D62CAE3D0B2960146E3D15CC6 /* UIView+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 403E083CF449B4B0144BC769B97043DC /* UIView+MJExtension.m */; }; + E598C1E3C7A57F92401D607E5675ADD3 /* JXSegmentedIndicatorDotLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BB35358219022632C6A9EF8A462D1F /* JXSegmentedIndicatorDotLineView.swift */; }; + E6ED06AC318A34F7744B32CEC759CDA9 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB032731F90E0191116E810777820F0B /* Response.swift */; }; + E702D99CA55B52C306544C4DDF9083DA /* MJRefreshBackGifFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C805EF5F028E6D4F8C47BE994E7975B /* MJRefreshBackGifFooter.m */; }; + E718E500074E8D6B011D53571B27BC0D /* Kingfisher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68C6AC662AF5F96BCF9E223D29845982 /* Kingfisher-dummy.m */; }; + E818AF38E910251104A9A8AFC9227C3C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + E8A077D1FE8B40F1D67F5B3FB0613628 /* SessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6265DB8E8D0BAED2C39405AECD61AF04 /* SessionDelegate.swift */; }; + E8AD52B68BDE7B679B358601CCAB3F2D /* UICollectionViewLayout+MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 77EFAEF82C69A4F5F14C4CDB669C9D9C /* UICollectionViewLayout+MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E90B040D725864FEAF54241327117856 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 367AAD6A6017F88B42A968FCBA8F44A8 /* PrivacyInfo.xcprivacy */; }; + E94B31DCAF75D93405D3BAE188604EB2 /* ConstraintConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54020DD80FFCE6C88B44AD8F555783A9 /* ConstraintConfig.swift */; }; + EA774692DA04CE293FBB5AE6F2FC97CF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 446631A716250A41D285C5A69C889E4A /* PrivacyInfo.xcprivacy */; }; + EB126B698D642942058D9B676EC1E32B /* MJRefreshConst.m in Sources */ = {isa = PBXBuildFile; fileRef = 84F9D005F2B85BE22CA185718A0535FD /* MJRefreshConst.m */; }; + EB3408F453211B44E5F094E153EE0B6B /* FSAudioController.m in Sources */ = {isa = PBXBuildFile; fileRef = 84BD083CF0E74D69596F699E0B0DAC6E /* FSAudioController.m */; }; + ECAA15FA3C4560E3287F2226EC8C1ECF /* IQKeyboardManagerConstantsInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD914D71D367DE71160B074235049829 /* IQKeyboardManagerConstantsInternal.swift */; }; + ED3AF010A8D34BC50D7C0D7BE3D2E890 /* JXPagingView-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E6439289EA56716750EA2531061260 /* JXPagingView-dummy.m */; }; + ED6D55B3F974F15896B01371C7FE20F3 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD733C769524A4890D8CA222061294F /* Common.swift */; }; + EE9FB55C99BB7ACD0E67ACDA1573AFC7 /* FSParsePlaylistRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 0226611A6E76C8969EAF0DF4668B5B8C /* FSParsePlaylistRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EF9B92EF4A0412D775FA55E4D1CB7A45 /* MJRefreshConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = B95736ACF688CE97879CE512184557E1 /* MJRefreshConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EFC90283F9AB43BB6FF377812BD3673F /* ConstraintPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B18DFF40BBC24DB3E0E6AFD51871F38 /* ConstraintPriority.swift */; }; + F00E48AB2D923607D9B91DC61DBDEB8F /* MJRefreshAutoGifFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = 2700CE2AA0D809F25EBC8B3B4F1A57BF /* MJRefreshAutoGifFooter.m */; }; + F04A9832815B9EF600EF7387C6882D3D /* DateInRegion+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F6348E3B982F1C6A8183DE435D49C1E /* DateInRegion+Math.swift */; }; + F060261A5C0842947977A3CAAD96566B /* Int64+TaskInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E00AA56B8E87F888C2272177B0DB376 /* Int64+TaskInfo.swift */; }; + F0AB498412A415474D6105F398614F74 /* JXSegmentedComponetGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B4928DAC11CD0F28D07D85993E6E3B /* JXSegmentedComponetGradientView.swift */; }; + F0DF27608B184B348B3127233761F4BA /* ExtensionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15F1937658AB4207DC3E99E1ABF8729 /* ExtensionHelpers.swift */; }; + F17A4B446550E63A5D3E216E39918030 /* Reachability-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = A794BB26E2D7D9E1AEEB5DB848440C14 /* Reachability-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F1DA2E8CFCD0F77604F92816C5A373F1 /* FSAudioStream.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA0AA2C2CEE56C08EFDF78A739C0067B /* FSAudioStream.mm */; }; + F2E9C9068E8434E9FC9B60755A354FA8 /* MJRefreshBackNormalFooter.m in Sources */ = {isa = PBXBuildFile; fileRef = D5C7A07FB940535B0ACB2DA9751FB827 /* MJRefreshBackNormalFooter.m */; }; + F366D04DEA0EBBCA9CA4F1F4E29695E8 /* ConstraintDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90B184E14693E477A94423C02031AB5 /* ConstraintDSL.swift */; }; + F3B5353C1CDE6C2DD2E80F32D3637750 /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D2D30775656A6F7F644EC3E77AC357 /* SessionDataTask.swift */; }; + F3EC0ED4BED8DD2C3222C7DCAC2452D2 /* JXSegmentedTitleAttributeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740B2DF7CACF2EE62202C33B3853A83C /* JXSegmentedTitleAttributeCell.swift */; }; + F403D62A7C82B41016A4AADD34D08263 /* FileChecksumHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D92259D7A615489F87C16B8B319B2824 /* FileChecksumHelper.swift */; }; + F404BFA0E5F2CFF051688C90B319AC85 /* AnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0565234423B639A17F1E91A3D559AB99 /* AnimatedImageView.swift */; }; + F47587932A67D8E3820DAFD9A0E1995E /* IQPlaceholderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4846DC539D38816DBC96DB7BF50FF1CE /* IQPlaceholderable.swift */; }; + F4DD0AD58DDD5641BDEAEA6CF44FF0ED /* Protected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646CA35D7A677037E651FB1DCAF038DA /* Protected.swift */; }; + F69925E3D8812AAFC099940721D12AEC /* Locales.swift in Sources */ = {isa = PBXBuildFile; fileRef = E782ECBFE74BC5E7C7A69C7C3D33C4FD /* Locales.swift */; }; + F6CF73614B01B233058CEDDB54309E60 /* SessionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28F397A75CD65DF0BABDEAF6E23EAF6 /* SessionConfiguration.swift */; }; + F7E576E007A81E0EFD2E0849CB17878D /* WebSocketRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488856EC78D9E7B766DB9E061CABC8F5 /* WebSocketRequest.swift */; }; + F94703ED86C58DDBE5A2503D148CD040 /* ConstraintRelation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAAE3ADACEB4C422DD913DE51816FA68 /* ConstraintRelation.swift */; }; + FA1E35E6DE6EC8A9E5E2B12A414E1B70 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 543DBF6743B34366D2E9B3D55471D3AF /* CFNetwork.framework */; }; + FA50CBB47B030D9475376E2D6ED3FF04 /* ResumeDataHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DCC525B5FC5BC436F4E144E018923F5 /* ResumeDataHelper.swift */; }; + FB2EEA5AF5278F76CD7366B23CC66815 /* ImageView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B99A8829F856511EC0C2AE7FF086B3E /* ImageView+Kingfisher.swift */; }; + FC524E181A75784881A12562BDB00CC6 /* MJRefreshAutoGifFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 24B649D2878AC1DBFB2C4C94AEDDDD85 /* MJRefreshAutoGifFooter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FC745954D9A2704BCACCB3A03336976E /* UIScrollView+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9F75C6ADBAE1B1E97DFE22EBDDCDFD /* UIScrollView+MJExtension.m */; }; + FCE62086E1AB54A4F61EBCDBA15C1510 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA3B80B6F832FECE2F97FD046F117394 /* EventMonitor.swift */; }; + FCEB3C1A7FCD1518AF8985822E2E14AA /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1232C6C8ADF2B7CC22FD973A4687B48 /* ImageProcessor.swift */; }; + FDEB8D2D369F9FEBF8FC3D822E5C96C6 /* JXPagingMainTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABAC09EFA34B7B44CD9B563DD0D4C54 /* JXPagingMainTableView.swift */; }; + FE8F4A5C40BA40C835CA301C92AED5E0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */; }; + FEC85148AFF458241FDD707C5BA8CD40 /* TimePeriod+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4870DA3D6F556BFAD1D0FD607F3F53 /* TimePeriod+Support.swift */; }; + FEDB5503231B230FDA7C7A25EAB38318 /* DateComponents+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D2C5AD1222F2083B9740B6B843B0E0 /* DateComponents+Extras.swift */; }; + FF886124915FEF2A6FBB663CA621B4FC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1EF6CE4DCA3A7EB57EF30D95912E1CC /* QuartzCore.framework */; }; + FFD7E1B8FA0F3960BE24DA2D20647332 /* ConstraintRelatableTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F8DFC026AC5D16DD4867C898032787 /* ConstraintRelatableTarget.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 04009532C9602DFA4571AF336AFB7C5C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B26054DF1DEA11585A231AF6D1D80D5E; - remoteInfo = "MJRefresh-MJRefresh.Privacy"; - }; - 0A0E1A8C5689F0474D3549298F7E19CB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 9828BBC09E9FB1238624113D7456E59E; - remoteInfo = "Kingfisher-Kingfisher"; - }; - 142BCA0E13C05E1A83FB4666F22A1552 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 1C8D67D8B72D6BA42CCEDB648537A340; - remoteInfo = SVProgressHUD; - }; - 201623F455C6F19F61031B03545147D2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 6868056D761E163D10FDAF8CF1C4D9B8; - remoteInfo = MJRefresh; - }; - 2799C4D2D7768534EDE92F178DE24D47 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B490E7485944099E16C9CBD79119D1D4; - remoteInfo = IQKeyboardManagerSwift; - }; - 2C2690B3E48F2A7DAC7365AE33F00B17 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B2B2AD5303610D8EBEA025B2660C8EC5; - remoteInfo = "JXPagingView-JXPagingView"; - }; - 2D57819E56EE60F3DF2C6E678E7D3569 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = E863A9A96F52A35F47491E7B41ECEF9A; - remoteInfo = JXSegmentedView; - }; - 30FFF29F3A02744304E52EE68F953BF3 /* PBXContainerItemProxy */ = { + 2B3DE7E3F6FF2DC64B43F8D54F5BA126 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = EAAA1AD3A8A1B59AB91319EE40752C6D; remoteInfo = Alamofire; }; - 44996704F085A77FFFD3669AD2AD3058 /* PBXContainerItemProxy */ = { + 2E2B3CA35F01D0C8D4C8185315D025CD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = 52F43AC38D9FF80196C69FB03AEEFDDA; - remoteInfo = "JXSegmentedView-JXSegmentedView"; + remoteGlobalIDString = B2B2AD5303610D8EBEA025B2660C8EC5; + remoteInfo = "JXPagingView-JXPagingView"; }; - 4679EE8967F1A89961ECB2793E44547B /* PBXContainerItemProxy */ = { + 31DFA7A6B4A5F2D8D24A13CA986AA424 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CAA047C0F5E4106F3904E8497FA17F97; + remoteInfo = Reachability; + }; + 331B2125E99AE39DBDFFA681C20CC080 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1C8D67D8B72D6BA42CCEDB648537A340; + remoteInfo = SVProgressHUD; + }; + 39C9918B8C6E37752F9D7F1E93ADD657 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 8A8DB685241263AFDF5E6B20FE67B93A; remoteInfo = "SnapKit-SnapKit_Privacy"; }; - 668AF990E71B0FD610A0100A739B125C /* PBXContainerItemProxy */ = { + 3A0CDAEC6F07673562A8BCC7E346368D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = C4E1020AF425614337737213AA26DBD5; - remoteInfo = JXPagingView; + remoteGlobalIDString = EF6413888FBA82A60EBB6F0A0EA14AD8; + remoteInfo = Tiercel; }; - 7912D6332CD6248A05C5E894EA0DE591 /* PBXContainerItemProxy */ = { + 3F33C18D96BBF20741E70D698B6B4D06 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = 19622742EBA51E823D6DAE3F8CDBFAD4; - remoteInfo = SnapKit; + remoteGlobalIDString = D2787856C227A709315E3C9C4355A440; + remoteInfo = "Reachability-Reachability_Privacy"; }; - A7BFC44123CBC428C300871C2A5C0B39 /* PBXContainerItemProxy */ = { + 3FA2B62420DB5B3A067509B9CCF7DD2C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = 6038CE6006EFBE9D905454CF01909C42; - remoteInfo = SwiftDate; + remoteGlobalIDString = CAA047C0F5E4106F3904E8497FA17F97; + remoteInfo = Reachability; }; - D80071531B55B03F9F25899249DEDFC3 /* PBXContainerItemProxy */ = { + 4381C03532A693F2DA89B10B86C9C6F4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B490E7485944099E16C9CBD79119D1D4; + remoteInfo = IQKeyboardManagerSwift; + }; + 4E9FA59D3929ED893F7ADADC913ED275 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = 976126A1CE06DC6E162563800E1BDF14; remoteInfo = "Alamofire-Alamofire"; }; - E34EB63D4E4B105244F3DA6DA3F80E0D /* PBXContainerItemProxy */ = { + 524E356992305B3EE8E57D80342EE4CE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C3AAC0817EA4DC8BD9C0046F50078BF9; + remoteInfo = FreeStreamer; + }; + 6A27223B7A93C4ADB9051FEFFAD57857 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 19622742EBA51E823D6DAE3F8CDBFAD4; + remoteInfo = SnapKit; + }; + A706A072057FD19371ABE1ECCB4A823E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E863A9A96F52A35F47491E7B41ECEF9A; + remoteInfo = JXSegmentedView; + }; + A7F47BB8EBC43499D97588770342BAAD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; remoteGlobalIDString = E8022D22FAA6690B5E1C379C1BCE1491; remoteInfo = Kingfisher; }; + B9537C3853702B3E29EA534F3745DB82 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6038CE6006EFBE9D905454CF01909C42; + remoteInfo = SwiftDate; + }; + CC4C5AB9184F5773A95B9D8CE9592479 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6868056D761E163D10FDAF8CF1C4D9B8; + remoteInfo = MJRefresh; + }; + CCAF531EA632DE38D320090D7B70C51B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C4E1020AF425614337737213AA26DBD5; + remoteInfo = JXPagingView; + }; + DF0C9C1D510C4D5F716D7605A02114EE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B26054DF1DEA11585A231AF6D1D80D5E; + remoteInfo = "MJRefresh-MJRefresh.Privacy"; + }; + F216D2B05F7D575335AC4A635F26284C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9828BBC09E9FB1238624113D7456E59E; + remoteInfo = "Kingfisher-Kingfisher"; + }; + FE204BADD6B5C3BEB9549AB026F3C722 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 52F43AC38D9FF80196C69FB03AEEFDDA; + remoteInfo = "JXSegmentedView-JXSegmentedView"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 000257BCD6570C4B0371D660CCF8D3E1 /* DotNetParserFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DotNetParserFormatter.swift; path = Sources/SwiftDate/Formatters/DotNetParserFormatter.swift; sourceTree = ""; }; - 0082002FD60B11229524482ADA592954 /* MJRefreshComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshComponent.h; path = MJRefresh/Base/MJRefreshComponent.h; sourceTree = ""; }; - 02D1EC547B26205284A72A9F9473EB73 /* Indicator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Indicator.swift; path = Sources/Views/Indicator.swift; sourceTree = ""; }; - 03538AE63A9932FE6DD18C82468E190F /* MJRefreshConst.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshConst.h; path = MJRefresh/MJRefreshConst.h; sourceTree = ""; }; - 035EB38FC9B73D3AFE4C25920C1A1C32 /* RequestModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestModifier.swift; path = Sources/Networking/RequestModifier.swift; sourceTree = ""; }; - 04446B3B53853AB15AE3B7B85CF9343D /* JXPagingView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "JXPagingView-dummy.m"; sourceTree = ""; }; - 05A41A0B8D148A4BA10DC25292FA63E6 /* Combine.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Combine.swift; path = Source/Features/Combine.swift; sourceTree = ""; }; - 07490687930290E2CF12D183E94326BF /* IQKeyboardManager+UITextFieldViewNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+UITextFieldViewNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+UITextFieldViewNotification.swift"; sourceTree = ""; }; + 0053FAA6FA0A2E35B78413CFFFE3D7DF /* FormatIndicatedCacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FormatIndicatedCacheSerializer.swift; path = Sources/Cache/FormatIndicatedCacheSerializer.swift; sourceTree = ""; }; + 00A4DA108AE69BEB562DE490822C90D3 /* MJRefresh.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.debug.xcconfig; sourceTree = ""; }; + 0226611A6E76C8969EAF0DF4668B5B8C /* FSParsePlaylistRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSParsePlaylistRequest.h; path = FreeStreamer/FreeStreamer/FSParsePlaylistRequest.h; sourceTree = ""; }; + 0246CF6ED39085AD58A2A4C18931E134 /* RelativeFormatterLanguage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RelativeFormatterLanguage.swift; path = Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatterLanguage.swift; sourceTree = ""; }; + 02B941FC8A7C7118F2703A44433604B1 /* Alamofire.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.debug.xcconfig; sourceTree = ""; }; + 038B18C5D6DE667EE97D03F020423EDD /* KFImageRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImageRenderer.swift; path = Sources/SwiftUI/KFImageRenderer.swift; sourceTree = ""; }; + 045B9D1FD204A0A244BBCA596A43DA72 /* Alamofire-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-prefix.pch"; sourceTree = ""; }; + 0565234423B639A17F1E91A3D559AB99 /* AnimatedImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedImageView.swift; path = Sources/Views/AnimatedImageView.swift; sourceTree = ""; }; + 05A3A7AA48C71521A2AB98871FF57BE8 /* MJRefresh-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MJRefresh-umbrella.h"; sourceTree = ""; }; + 05D488858AA61B9A41D705A0719AB788 /* TimeInterval+Formatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TimeInterval+Formatter.swift"; path = "Sources/SwiftDate/Foundation+Extras/TimeInterval+Formatter.swift"; sourceTree = ""; }; + 065328F2F98184B72B3BDFD13B1DE42D /* caching_stream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = caching_stream.h; path = FreeStreamer/FreeStreamer/caching_stream.h; sourceTree = ""; }; + 06F5FCDB146537A5FB81A9608C55800C /* ImageDataProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProcessor.swift; path = Sources/Networking/ImageDataProcessor.swift; sourceTree = ""; }; + 06FCD1CF754595FC1050F9E2BE4EE7FE /* DataStreamRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataStreamRequest.swift; path = Source/Core/DataStreamRequest.swift; sourceTree = ""; }; + 07665A785548E8E674CB0127D9EED147 /* JXSegmentedCollectionView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedCollectionView.swift; path = Sources/Core/JXSegmentedCollectionView.swift; sourceTree = ""; }; 07928762D9A8551470DAAD7C1E1F53A5 /* JXSegmentedView */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = JXSegmentedView; path = JXSegmentedView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 0837DA8FB2DF8148CEE1EA57A72EE570 /* ConstraintConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintConfig.swift; path = Sources/ConstraintConfig.swift; sourceTree = ""; }; + 08285191D7204A02272F9EB631D02466 /* Date.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Date.swift; path = Sources/SwiftDate/Date/Date.swift; sourceTree = ""; }; 085DBCE7DD98588B2ED103B1C1F36026 /* Alamofire-Alamofire */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "Alamofire-Alamofire"; path = Alamofire.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - 08D053BD683C26F73E7D5BAADC8A4FEF /* ImageDrawing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDrawing.swift; path = Sources/Image/ImageDrawing.swift; sourceTree = ""; }; - 08E28C2D9A6137B156647AC16D95A15E /* URLEncodedFormEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLEncodedFormEncoder.swift; path = Source/Features/URLEncodedFormEncoder.swift; sourceTree = ""; }; - 09B2FF8925A3677E79E08183E04491D1 /* JXSegmentedIndicatorTriangleView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorTriangleView.swift; path = Sources/Indicator/JXSegmentedIndicatorTriangleView.swift; sourceTree = ""; }; - 0A704FDB2D535D699FD2D53CC6DFFC70 /* JXSegmentedNumberItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedNumberItemModel.swift; path = Sources/Number/JXSegmentedNumberItemModel.swift; sourceTree = ""; }; - 0A8B5D1A6CBAD8FC5DA5D3D0DA7512BE /* JXSegmentedTitleOrImageCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleOrImageCell.swift; path = Sources/TitleOrImage/JXSegmentedTitleOrImageCell.swift; sourceTree = ""; }; - 0AB50AF41FD17FD4D634D6CB5152810D /* OperationQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "OperationQueue+Alamofire.swift"; path = "Source/Extensions/OperationQueue+Alamofire.swift"; sourceTree = ""; }; - 0DB2B3B9A91393821FCD01D47CB39B40 /* JXSegmentedView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXSegmentedView.debug.xcconfig; sourceTree = ""; }; - 0DB30FE6997717CE3E7589C60A9C7A9E /* Protected.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Protected.swift; path = Source/Core/Protected.swift; sourceTree = ""; }; - 0E81980AF59D081E0E8A9761D1C422BF /* JXSegmentedIndicatorBaseView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorBaseView.swift; path = Sources/Indicator/JXSegmentedIndicatorBaseView.swift; sourceTree = ""; }; - 0EA5D2A634B9432EA862513BDE2A5ED4 /* URLRequest+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLRequest+Alamofire.swift"; path = "Source/Extensions/URLRequest+Alamofire.swift"; sourceTree = ""; }; - 0EC583B42A01E6257C74C1DCD53164A8 /* SVProgressHUD-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SVProgressHUD-dummy.m"; sourceTree = ""; }; + 085EBD55C44E75117740E1AA43932349 /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Alamofire.swift"; path = "Source/Extensions/DispatchQueue+Alamofire.swift"; sourceTree = ""; }; + 08FA7E2B552FE3E8F4062758D55B2B19 /* IQKeyboardManager+Position.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Position.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Position.swift"; sourceTree = ""; }; + 095D58DA4F832B26482730E74AA23D62 /* FSPlaylistItem.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSPlaylistItem.m; path = FreeStreamer/FreeStreamer/FSPlaylistItem.m; sourceTree = ""; }; + 09BA7275A4B51D06CE96633FFCFEB8C7 /* ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; sourceTree = ""; }; + 0A21DE84B8BBD51EE7C86AF1310AB8DB /* MJRefreshBackNormalFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackNormalFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.h; sourceTree = ""; }; + 0A52C805625D7F43FD427AAE5059729A /* JXSegmentedView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "JXSegmentedView-Info.plist"; sourceTree = ""; }; + 0B222DC6983E3B7562A0B9F8F7F69F30 /* SVProgressHUD.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SVProgressHUD.modulemap; sourceTree = ""; }; + 0BC635388215C143B7967569B49BCBA1 /* Reachability.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; + 0DB1F6055B6597CA3AB467E151EFF785 /* MJRefreshFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshFooter.h; path = MJRefresh/Base/MJRefreshFooter.h; sourceTree = ""; }; + 0DD4FB14C7316C4A190EC2E458275B19 /* ConstraintAttributes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintAttributes.swift; path = Sources/ConstraintAttributes.swift; sourceTree = ""; }; + 0E00AA56B8E87F888C2272177B0DB376 /* Int64+TaskInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Int64+TaskInfo.swift"; path = "Sources/Extensions/Int64+TaskInfo.swift"; sourceTree = ""; }; 0EE185594AC917D6AC98B5601D843EDA /* Pods-MusicPlayer-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-MusicPlayer-frameworks.sh"; sourceTree = ""; }; - 1199E580C5F19AFD1010FE20258DADBD /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; - 11AF900AC1C5B4CD4047B709266568E5 /* DispatchQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Alamofire.swift"; path = "Source/Extensions/DispatchQueue+Alamofire.swift"; sourceTree = ""; }; - 11E47356E556750015100B2119302E53 /* JXSegmentedIndicatorImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorImageView.swift; path = Sources/Indicator/JXSegmentedIndicatorImageView.swift; sourceTree = ""; }; - 1294C0C419D6967DAAFC5C21609CAAAF /* ConstraintMultiplierTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMultiplierTarget.swift; path = Sources/ConstraintMultiplierTarget.swift; sourceTree = ""; }; - 12A6ED7989C6F4843507D223BD102CC5 /* URLSessionConfiguration+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLSessionConfiguration+Alamofire.swift"; path = "Source/Extensions/URLSessionConfiguration+Alamofire.swift"; sourceTree = ""; }; - 13184921F92E5D84095DD92314A9A4EA /* MJRefreshTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshTrailer.m; path = MJRefresh/Base/MJRefreshTrailer.m; sourceTree = ""; }; - 135384F59FC764376A1E1806D1B43835 /* JXSegmentedIndicatorGradientView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorGradientView.swift; path = Sources/Indicator/JXSegmentedIndicatorGradientView.swift; sourceTree = ""; }; - 136FDCF413CE3C1DC00030DAFFF9A94F /* MJRefreshBackFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackFooter.m; path = MJRefresh/Base/MJRefreshBackFooter.m; sourceTree = ""; }; + 104108C5D1C6A09D9C7329B2B430CFE1 /* AuthenticationInterceptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationInterceptor.swift; path = Source/Features/AuthenticationInterceptor.swift; sourceTree = ""; }; + 10FFD14C2E11E5F89FF13935005EC4D8 /* ImageDownloaderDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloaderDelegate.swift; path = Sources/Networking/ImageDownloaderDelegate.swift; sourceTree = ""; }; + 110F64C4F2FD9C3DE754C0DF6D6BE4CB /* ImageContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageContext.swift; path = Sources/SwiftUI/ImageContext.swift; sourceTree = ""; }; + 113456ED5740F97EACFA393659870BA7 /* URLSessionConfiguration+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLSessionConfiguration+Alamofire.swift"; path = "Source/Extensions/URLSessionConfiguration+Alamofire.swift"; sourceTree = ""; }; + 12F886FAD42295605D07DF85466F716A /* IQKeyboardManagerSwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "IQKeyboardManagerSwift-Info.plist"; sourceTree = ""; }; + 1318FA20C5E765D28786F3F849CDB26E /* AuthenticationChallengeResponsable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationChallengeResponsable.swift; path = Sources/Networking/AuthenticationChallengeResponsable.swift; sourceTree = ""; }; 137651D4B8A2CAB9ADEE7E77FCB50B0C /* Pods-MusicPlayer-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-MusicPlayer-dummy.m"; sourceTree = ""; }; - 1383471ECB9F37892C9CC3EF67CA0375 /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Source/Core/Response.swift; sourceTree = ""; }; - 1384016DEBC0AD27C484B338B4FBEDB5 /* ConstraintLayoutGuide.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutGuide.swift; path = Sources/ConstraintLayoutGuide.swift; sourceTree = ""; }; - 13AB468987C8A41B4D5D6B895F8B9A6F /* JXSegmentedView.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = JXSegmentedView.modulemap; sourceTree = ""; }; - 14052CB3D5057DCF4593BD1E77D90942 /* ConstraintDirectionalInsets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDirectionalInsets.swift; path = Sources/ConstraintDirectionalInsets.swift; sourceTree = ""; }; - 142F33BE61F0E752B876C5145BE8D812 /* JXPagingSmoothView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingSmoothView.swift; path = Sources/JXPagingView/JXPagingSmoothView.swift; sourceTree = ""; }; - 1434E2DF42114AA07FFC65F0AF74B738 /* MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefresh.h; path = MJRefresh/MJRefresh.h; sourceTree = ""; }; - 14C0D566F3A17E2E2DBDE4098F711125 /* Date+Components.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Components.swift"; path = "Sources/SwiftDate/Date/Date+Components.swift"; sourceTree = ""; }; + 13CD38CD80EF3B99DB3C7067477E8CB9 /* ConstraintView+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintView+Extensions.swift"; path = "Sources/ConstraintView+Extensions.swift"; sourceTree = ""; }; + 13E6439289EA56716750EA2531061260 /* JXPagingView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "JXPagingView-dummy.m"; sourceTree = ""; }; + 14A44A5FC1159E231EEC8EA3A905A120 /* HTTPMethod.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPMethod.swift; path = Source/Core/HTTPMethod.swift; sourceTree = ""; }; + 151DB2B13390607CE0EBC6E10AFD1E04 /* URLEncodedFormEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLEncodedFormEncoder.swift; path = Source/Features/URLEncodedFormEncoder.swift; sourceTree = ""; }; + 154B43B57C8ADCBE6975B5E4CB8D8BE8 /* FSXMLHttpRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSXMLHttpRequest.m; path = FreeStreamer/FreeStreamer/FSXMLHttpRequest.m; sourceTree = ""; }; + 1572E364BB1E0FA13BCA456AF38E8969 /* Tiercel-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Tiercel-prefix.pch"; sourceTree = ""; }; + 15CA5A8DF5A4A18FF5C1D85FDD30CC6A /* JXSegmentedTitleCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleCell.swift; path = Sources/Title/JXSegmentedTitleCell.swift; sourceTree = ""; }; 15E14083D6A2AB230F81705892CB4520 /* Pods-MusicPlayer.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-MusicPlayer.modulemap"; sourceTree = ""; }; - 1624F448C0756E91DC913FD781A9A9EC /* MJRefreshAutoGifFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoGifFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.h; sourceTree = ""; }; - 1A26B70DABA1CBACD8166FC00D07C081 /* KFOptionsSetter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFOptionsSetter.swift; path = Sources/General/KFOptionsSetter.swift; sourceTree = ""; }; - 1A8F4312D81FC10BD8029BE851C76498 /* AlamofireExtended.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AlamofireExtended.swift; path = Source/Features/AlamofireExtended.swift; sourceTree = ""; }; - 1AAB28B6A1A5AA4CC4A0EEB258D9DC57 /* SVProgressAnimatedView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVProgressAnimatedView.h; path = SVProgressHUD/SVProgressAnimatedView.h; sourceTree = ""; }; + 15E6514D83861C620C7A013C94AAB9F4 /* Calendars.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Calendars.swift; path = Sources/SwiftDate/Supports/Calendars.swift; sourceTree = ""; }; + 17471A60400FF41CA4D77544B7817005 /* ImageDownloader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloader.swift; path = Sources/Networking/ImageDownloader.swift; sourceTree = ""; }; + 18406CC620E7BB6D62552425CDBB8830 /* audio_queue.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = audio_queue.cpp; path = FreeStreamer/FreeStreamer/audio_queue.cpp; sourceTree = ""; }; + 186CD6B1C19F3A72817C08755AF2F5A5 /* URLConvertible+URLRequestConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLConvertible+URLRequestConvertible.swift"; path = "Source/Core/URLConvertible+URLRequestConvertible.swift"; sourceTree = ""; }; + 18969DF6D4B5B1F3CFC962FAC18910BB /* JXSegmentedView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXSegmentedView.debug.xcconfig; sourceTree = ""; }; + 192580DED59C4D3B135E94F830FCA5B3 /* JXSegmentedBaseCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedBaseCell.swift; path = Sources/Core/JXSegmentedBaseCell.swift; sourceTree = ""; }; + 19AD0AEC321066C273D5EC90BC2DF7A9 /* IQKeyboardManagerSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IQKeyboardManagerSwift-umbrella.h"; sourceTree = ""; }; + 19F386566B21015AF7730984275FC385 /* Runtime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Runtime.swift; path = Sources/Utility/Runtime.swift; sourceTree = ""; }; + 1A7C07ADE12872C5F8AFB53F698FB941 /* JXPagingView.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = JXPagingView.modulemap; sourceTree = ""; }; + 1A9FE5CC92E229E62CDC676046989D88 /* ImageBinder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageBinder.swift; path = Sources/SwiftUI/ImageBinder.swift; sourceTree = ""; }; + 1AA4BDFF82A93332CC58469234702EB5 /* Filter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Sources/Image/Filter.swift; sourceTree = ""; }; + 1B99A8829F856511EC0C2AE7FF086B3E /* ImageView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ImageView+Kingfisher.swift"; path = "Sources/Extensions/ImageView+Kingfisher.swift"; sourceTree = ""; }; 1BBB6DE3AF0E150160FD2FA346CC6CD6 /* Pods-MusicPlayer-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-MusicPlayer-umbrella.h"; sourceTree = ""; }; - 1C8C49DCED4EC3826245D19DFC7FA7E9 /* ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist"; sourceTree = ""; }; - 1C8F4284CBAD963633A64783E4CFB37D /* MJRefreshBackNormalFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackNormalFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.m; sourceTree = ""; }; - 1DA3BDF290E39DAB4D1F114C808EB725 /* UICollectionViewLayout+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UICollectionViewLayout+MJRefresh.m"; path = "MJRefresh/UICollectionViewLayout+MJRefresh.m"; sourceTree = ""; }; - 1DE3D079E059E37BB8233A28ECEF0FC7 /* SVIndefiniteAnimatedView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVIndefiniteAnimatedView.m; path = SVProgressHUD/SVIndefiniteAnimatedView.m; sourceTree = ""; }; - 1E5CD298A2F13E3902AAA3510FE5C9FD /* AuthenticationChallengeResponsable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationChallengeResponsable.swift; path = Sources/Networking/AuthenticationChallengeResponsable.swift; sourceTree = ""; }; - 1EC6A17326897FCD562597FF862EFB28 /* JXSegmentedNumberDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedNumberDataSource.swift; path = Sources/Number/JXSegmentedNumberDataSource.swift; sourceTree = ""; }; - 1F08C4884167A8DF12652D4ADA87DC8E /* MJRefreshBackGifFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackGifFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.h; sourceTree = ""; }; - 1F0A7FCD71890676CE08D3ADDB7CA7B4 /* GraphicsContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GraphicsContext.swift; path = Sources/Image/GraphicsContext.swift; sourceTree = ""; }; - 1F79F3D31F68D650253E6399BB7549D1 /* KFImageProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImageProtocol.swift; path = Sources/SwiftUI/KFImageProtocol.swift; sourceTree = ""; }; - 1FF0E1D3C22D93C0C7B2C51A16E17913 /* IQPreviousNextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQPreviousNextView.swift; path = IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift; sourceTree = ""; }; - 207F4383A0CAE6E946319D62026A82F0 /* SwiftDate-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftDate-umbrella.h"; sourceTree = ""; }; - 2215909E83BE0517F84079C1593E4E85 /* JXSegmentedDotCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedDotCell.swift; path = Sources/Dot/JXSegmentedDotCell.swift; sourceTree = ""; }; - 225AEBA0EC32A3D5A52ECA46DBBED7F3 /* JXSegmentedTitleDynamicConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleDynamicConfiguration.swift; path = Sources/Title/JXSegmentedTitleDynamicConfiguration.swift; sourceTree = ""; }; - 234A9BD48AA1E6813E36334F22C346D4 /* ConstraintInsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintInsetTarget.swift; path = Sources/ConstraintInsetTarget.swift; sourceTree = ""; }; - 24091B247B320B28B53B7DB2A0EB996B /* ConstraintMakerPrioritizable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerPrioritizable.swift; path = Sources/ConstraintMakerPrioritizable.swift; sourceTree = ""; }; - 268D0497BC989AF2355C7E9712755278 /* CacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CacheSerializer.swift; path = Sources/Cache/CacheSerializer.swift; sourceTree = ""; }; - 26E33A2601884DE73A47BD1A2690B14F /* ImageTransition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageTransition.swift; path = Sources/Image/ImageTransition.swift; sourceTree = ""; }; - 2752DAD78FD744FDF40B54ADB0708E81 /* Locales.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Locales.swift; path = Sources/SwiftDate/Supports/Locales.swift; sourceTree = ""; }; - 27798DCE9459ACACF37012755F087DB9 /* MJRefreshConst.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshConst.m; path = MJRefresh/MJRefreshConst.m; sourceTree = ""; }; - 280637DA318DC5D912D98194C6844751 /* MJRefreshNormalTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshNormalTrailer.m; path = MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.m; sourceTree = ""; }; - 2820DA7141C1E36BCF6D4D5D96802EAF /* JXSegmentedTitleGradientCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleGradientCell.swift; path = Sources/TitleGradient/JXSegmentedTitleGradientCell.swift; sourceTree = ""; }; - 282ACC9FB46C251345FC5EB3AEF1B9CE /* SVIndefiniteAnimatedView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVIndefiniteAnimatedView.h; path = SVProgressHUD/SVIndefiniteAnimatedView.h; sourceTree = ""; }; - 2893522A05B1339D4388FD46FF52C52A /* WKInterfaceImage+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "WKInterfaceImage+Kingfisher.swift"; path = "Sources/Extensions/WKInterfaceImage+Kingfisher.swift"; sourceTree = ""; }; - 28BCEACC594F9EA2E10117D26B3DA759 /* CachedResponseHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CachedResponseHandler.swift; path = Source/Features/CachedResponseHandler.swift; sourceTree = ""; }; - 2998944D54451B52A0996B2A9FDDD7DB /* TimeInterval+Formatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TimeInterval+Formatter.swift"; path = "Sources/SwiftDate/Foundation+Extras/TimeInterval+Formatter.swift"; sourceTree = ""; }; - 2C9B6C74D874F93335EDA5FBAC20675C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = SVProgressHUD/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 2E39692FF9A73D5EC6D4AE2DE35E3C0C /* JXSegmentedTitleGradientItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleGradientItemModel.swift; path = Sources/TitleGradient/JXSegmentedTitleGradientItemModel.swift; sourceTree = ""; }; - 2E89132366DFF0DC6A319EF946DF4CEA /* JXPagingView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "JXPagingView-Info.plist"; sourceTree = ""; }; - 2EB8A72B411C5E90471A44666337E7D2 /* ConstraintConstantTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintConstantTarget.swift; path = Sources/ConstraintConstantTarget.swift; sourceTree = ""; }; + 1BFE07EB5815ED4E3061D694AFE169C5 /* ImageTransition.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageTransition.swift; path = Sources/Image/ImageTransition.swift; sourceTree = ""; }; + 1C04292B44287517AD7ABDAF1E52CD95 /* MJRefreshNormalHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshNormalHeader.m; path = MJRefresh/Custom/Header/MJRefreshNormalHeader.m; sourceTree = ""; }; + 1C48D497CEEA7EB34E2FC6194741A4F4 /* SizeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SizeExtensions.swift; path = Sources/Utility/SizeExtensions.swift; sourceTree = ""; }; + 1CF9D0C18975B91BFAC8937997951692 /* audio_queue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = audio_queue.h; path = FreeStreamer/FreeStreamer/audio_queue.h; sourceTree = ""; }; + 1D8316A6A2618957E57AB5B31051B3AC /* IQTextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQTextView.swift; path = IQKeyboardManagerSwift/IQTextView/IQTextView.swift; sourceTree = ""; }; + 1FCD33E634B250907AA661A3C5F2C19E /* FSCheckContentTypeRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSCheckContentTypeRequest.m; path = FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.m; sourceTree = ""; }; + 202A71735BE68C199BF37F40C6CE4F19 /* MJRefreshAutoFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoFooter.h; path = MJRefresh/Base/MJRefreshAutoFooter.h; sourceTree = ""; }; + 203B8403E6E3D7A6813164E42EA97906 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = SVProgressHUD/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 20C642D426A1D3F5335387C32BBF5A18 /* ConstraintLayoutGuide.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutGuide.swift; path = Sources/ConstraintLayoutGuide.swift; sourceTree = ""; }; + 20D52F5BF32D92327D403383E88D33F4 /* IQKeyboardManagerSwift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = IQKeyboardManagerSwift.modulemap; sourceTree = ""; }; + 21B8B7C727D8E6DCB02966F19E4BDF14 /* JXPagingListRefreshView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingListRefreshView.swift; path = Sources/JXPagingView/JXPagingListRefreshView.swift; sourceTree = ""; }; + 22B75C82B4FA45FBD610EE3820F36ABA /* KingfisherOptionsInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherOptionsInfo.swift; path = Sources/General/KingfisherOptionsInfo.swift; sourceTree = ""; }; + 23E6D81A1886132FB59F96D28A386AB9 /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Source/Features/RedirectHandler.swift; sourceTree = ""; }; + 24B649D2878AC1DBFB2C4C94AEDDDD85 /* MJRefreshAutoGifFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoGifFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.h; sourceTree = ""; }; + 2516E789B118A50B5D0289D7464D36EA /* FSAudioStream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSAudioStream.h; path = FreeStreamer/FreeStreamer/FSAudioStream.h; sourceTree = ""; }; + 255E5C87081214F2B68D64E82AA9DD61 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 2618F09609D8CBB18F768D2EE4A14162 /* ConstraintMakerRelatable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerRelatable.swift; path = Sources/ConstraintMakerRelatable.swift; sourceTree = ""; }; + 2672A89EE6A8AD525224E066A860B77E /* SwiftDate.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwiftDate.modulemap; sourceTree = ""; }; + 268DBFBEBF1CFFE3E4096853AE17D797 /* Reachability.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Reachability.modulemap; sourceTree = ""; }; + 2700CE2AA0D809F25EBC8B3B4F1A57BF /* MJRefreshAutoGifFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoGifFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.m; sourceTree = ""; }; + 272E28E27BC34E8A01009E5AF1BFF723 /* DownloadRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DownloadRequest.swift; path = Source/Core/DownloadRequest.swift; sourceTree = ""; }; + 274BEE86492F3C61DE6D50C0A34C9991 /* Date+Math.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Math.swift"; path = "Sources/SwiftDate/Date/Date+Math.swift"; sourceTree = ""; }; + 27D37A59036A1116558DA573382EA9F2 /* String+Parser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+Parser.swift"; path = "Sources/SwiftDate/Foundation+Extras/String+Parser.swift"; sourceTree = ""; }; + 27DAAB9A8B0D734A9BBE8E8F4453F9D4 /* OperationQueue+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "OperationQueue+Alamofire.swift"; path = "Source/Extensions/OperationQueue+Alamofire.swift"; sourceTree = ""; }; + 27DAB2C811D037E7923BC7DC85E95B24 /* ParameterEncoding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoding.swift; path = Source/Core/ParameterEncoding.swift; sourceTree = ""; }; + 282346A50AF20F6B23E26CD6DBED315E /* JXPagingView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXPagingView.debug.xcconfig; sourceTree = ""; }; + 28964B0FC810363A9BFD622768788FC3 /* Typealiases.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Typealiases.swift; path = Sources/Typealiases.swift; sourceTree = ""; }; + 2952FA317462D1808CC73087B4EC0DA8 /* UIScrollView+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+MJRefresh.h"; path = "MJRefresh/UIScrollView+MJRefresh.h"; sourceTree = ""; }; + 2C74B3D86D767E3958819BDDB455791E /* JXSegmentedIndicatorGradientLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorGradientLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorGradientLineView.swift; sourceTree = ""; }; + 2D97EB1D469B927CC01C4FEE81A6AB26 /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Source/Core/SessionDelegate.swift; sourceTree = ""; }; + 2DCC525B5FC5BC436F4E144E018923F5 /* ResumeDataHelper.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResumeDataHelper.swift; path = Sources/Utility/ResumeDataHelper.swift; sourceTree = ""; }; + 2F04E5AE92F6C4FC3ECD3B58E312ED72 /* FSAudioController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSAudioController.h; path = FreeStreamer/FreeStreamer/FSAudioController.h; sourceTree = ""; }; 2F4A1CCB21DB7EA5A2ACEB11E374FBCA /* JXPagingView */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = JXPagingView; path = JXPagingView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 2FBE5C3E144BF69BC982850AFCE42CDB /* IQKeyboardManagerSwift-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "IQKeyboardManagerSwift-Info.plist"; sourceTree = ""; }; - 3050C5DBB1E4F1DF9FD1B33611FDBDAD /* SVProgressHUD.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SVProgressHUD.modulemap; sourceTree = ""; }; - 30D9FC3ABDA982F6CA82384628D6F165 /* RelativeFormatter+Style.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "RelativeFormatter+Style.swift"; path = "Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter+Style.swift"; sourceTree = ""; }; - 31126E0DD1B56D3713965A56F0B05AC8 /* SnapKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapKit-umbrella.h"; sourceTree = ""; }; - 321C97AD92F4D32E4E33BB5B77FFE0FC /* Storage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Storage.swift; path = Sources/Cache/Storage.swift; sourceTree = ""; }; - 33033614BAFA0628A44D6CE5D2EDBB09 /* Kingfisher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Kingfisher-dummy.m"; sourceTree = ""; }; - 344A8E805D9E365E5FDF94223860238E /* JXSegmentedDotDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedDotDataSource.swift; path = Sources/Dot/JXSegmentedDotDataSource.swift; sourceTree = ""; }; - 356BE641A77A6E56A217190DD89D8259 /* JXSegmentedView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "JXSegmentedView-dummy.m"; sourceTree = ""; }; - 36730D770821B0E94A45B2817FC4E2FD /* ImagePrefetcher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImagePrefetcher.swift; path = Sources/Networking/ImagePrefetcher.swift; sourceTree = ""; }; - 368AFBDCEBBA20270829ACA09283D127 /* ParameterEncoding.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoding.swift; path = Source/Core/ParameterEncoding.swift; sourceTree = ""; }; - 36E3BC01C35A4242976E6B95431C9F17 /* JXSegmentedComponetGradientView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedComponetGradientView.swift; path = Sources/Indicator/JXSegmentedComponetGradientView.swift; sourceTree = ""; }; - 37C1098A010D1C8E4E069D976A8D8DFD /* JXSegmentedView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXSegmentedView-umbrella.h"; sourceTree = ""; }; - 393CAB07C81E1F3ACA4DDB1CAB615E1B /* EventMonitor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EventMonitor.swift; path = Source/Features/EventMonitor.swift; sourceTree = ""; }; - 398152B547C8B96AD8C376F10BEB5701 /* IQKeyboardManagerConstantsInternal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManagerConstantsInternal.swift; path = IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift; sourceTree = ""; }; - 3A92A995C2CF52A57109B1F08F9E6044 /* ConstraintDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDSL.swift; path = Sources/ConstraintDSL.swift; sourceTree = ""; }; - 3A99AB9AED98C1B656C310507C966514 /* MJRefreshFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshFooter.h; path = MJRefresh/Base/MJRefreshFooter.h; sourceTree = ""; }; - 3AB6A380647126E36416E4610575F354 /* JXSegmentedView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXSegmentedView-prefix.pch"; sourceTree = ""; }; - 3AE4A701F7BAB983ED11B504E6487F76 /* MJRefresh.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.debug.xcconfig; sourceTree = ""; }; - 3B4DD5E3C3843A80F1882655B380DCDA /* UIScrollView+MJExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+MJExtension.m"; path = "MJRefresh/UIScrollView+MJExtension.m"; sourceTree = ""; }; - 3CF9BAB200D238346E29ED2DBA085B17 /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Sources/Cache/ImageCache.swift; sourceTree = ""; }; - 3D613FC602AD1B3823E553095A17BB58 /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.swift; path = Sources/Image/Image.swift; sourceTree = ""; }; - 3EF5F786A9CD8F2E78E95733362FE187 /* JXPagingView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXPagingView.debug.xcconfig; sourceTree = ""; }; - 3EFEBDB3A2CB8EFD1E1BB0C882FC6500 /* JXPagingView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXPagingView-umbrella.h"; sourceTree = ""; }; - 3F8AC6801BE5A7B67B8032757817BE51 /* RetryStrategy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RetryStrategy.swift; path = Sources/Networking/RetryStrategy.swift; sourceTree = ""; }; - 40E3CE126BEDA57D7DA923F7BBC49D32 /* IQUIViewController+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIViewController+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift"; sourceTree = ""; }; - 418EE8CAF2C7190F191FC7554EAE20D3 /* RelativeFormatterLanguage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RelativeFormatterLanguage.swift; path = Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatterLanguage.swift; sourceTree = ""; }; - 4316C38FD6E88F2FFBBB3BC6DF2C26FF /* DownloadRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DownloadRequest.swift; path = Source/Core/DownloadRequest.swift; sourceTree = ""; }; - 43FF45102B67E91CC3D79928C2717E40 /* SwiftDate-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwiftDate-dummy.m"; sourceTree = ""; }; - 440D319A1267C894C2AFFDA3362EBE9E /* Typealiases.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Typealiases.swift; path = Sources/Typealiases.swift; sourceTree = ""; }; - 451FEDBFD2A922230CA33D46534CD7F7 /* JXSegmentedView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXSegmentedView.release.xcconfig; sourceTree = ""; }; - 460F21D69A6B1F9493AACA08A2F865B9 /* JXSegmentedBaseDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedBaseDataSource.swift; path = Sources/Core/JXSegmentedBaseDataSource.swift; sourceTree = ""; }; - 475A4FB59CFB3BBA7FAC2F058F91F6E3 /* AssociatedValues.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssociatedValues.swift; path = Sources/SwiftDate/Supports/AssociatedValues.swift; sourceTree = ""; }; - 482206CB35A8E05C272FA37C0A8BF762 /* ConstraintLayoutGuideDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutGuideDSL.swift; path = Sources/ConstraintLayoutGuideDSL.swift; sourceTree = ""; }; - 4897326E2C8838615713B47EBE1FAD3D /* ImageProgressive.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProgressive.swift; path = Sources/Image/ImageProgressive.swift; sourceTree = ""; }; - 49B4649E1D11EB9D32BD3E212382EF1D /* Resource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Resource.swift; path = Sources/General/ImageSource/Resource.swift; sourceTree = ""; }; - 4BE5E05285408F9AA5B005A82CEFDA99 /* ConstraintDescription.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDescription.swift; path = Sources/ConstraintDescription.swift; sourceTree = ""; }; - 4C1194040926B2C7D711B859F8AE4312 /* JXSegmentedTitleImageDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleImageDataSource.swift; path = Sources/TitleImage/JXSegmentedTitleImageDataSource.swift; sourceTree = ""; }; - 4C554CE5C82E098DD3FF24BA75EE7490 /* DateInRegion+Math.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Math.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Math.swift"; sourceTree = ""; }; - 4CBB6593899E987A5B4300FC21B80983 /* MJRefreshComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshComponent.m; path = MJRefresh/Base/MJRefreshComponent.m; sourceTree = ""; }; - 4CDB52D83D152A96E4E9CD244CA95DEA /* KFImageOptions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImageOptions.swift; path = Sources/SwiftUI/KFImageOptions.swift; sourceTree = ""; }; - 4DE64A8F508B88F991A85594504A5076 /* ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; sourceTree = ""; }; - 4E16F379BC5CA8C2B21212962C6FA430 /* IQTextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQTextView.swift; path = IQKeyboardManagerSwift/IQTextView/IQTextView.swift; sourceTree = ""; }; - 4F1FB2699EA33ABD4EDB7F856D39296F /* JXSegmentedCollectionView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedCollectionView.swift; path = Sources/Core/JXSegmentedCollectionView.swift; sourceTree = ""; }; - 4FDEAE488C153F23CC530F9FC7878CE4 /* JXSegmentedTitleAttributeItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleAttributeItemModel.swift; path = Sources/AttributeTitle/JXSegmentedTitleAttributeItemModel.swift; sourceTree = ""; }; - 50DD20A7091C1AF20BBDAE8FF40C638E /* MJRefreshBackGifFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackGifFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.m; sourceTree = ""; }; - 50DD91D5AAEBFBFB4776365998B843B7 /* ConstraintMaker.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMaker.swift; path = Sources/ConstraintMaker.swift; sourceTree = ""; }; - 513DEC141FDDFD1CDAA8ED56A66AF623 /* TimePeriodProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodProtocol.swift; path = Sources/SwiftDate/TimePeriod/TimePeriodProtocol.swift; sourceTree = ""; }; - 51A207FA0764909F90D1FCCDE191F924 /* JXSegmentedBaseItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedBaseItemModel.swift; path = Sources/Core/JXSegmentedBaseItemModel.swift; sourceTree = ""; }; - 52C0154703620B58E000A12A7DD03B58 /* IQKeyboardManagerSwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IQKeyboardManagerSwift-prefix.pch"; sourceTree = ""; }; - 52CB2CCAA127ADA834B4CCF4686ECE60 /* IQKeyboardManager+OrientationNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+OrientationNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+OrientationNotification.swift"; sourceTree = ""; }; - 5347F4D40BA296B41A792189CB1331E9 /* Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Alamofire.swift; path = Source/Alamofire.swift; sourceTree = ""; }; - 536595B34971D58E93F91CEFFBA20A22 /* MJRefreshGifHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshGifHeader.h; path = MJRefresh/Custom/Header/MJRefreshGifHeader.h; sourceTree = ""; }; - 54B843EC8B993674D3288F87AEA9D122 /* NSButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSButton+Kingfisher.swift"; path = "Sources/Extensions/NSButton+Kingfisher.swift"; sourceTree = ""; }; - 5545219DAAC101FE50450F293367D8C9 /* ConstraintMakerFinalizable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerFinalizable.swift; path = Sources/ConstraintMakerFinalizable.swift; sourceTree = ""; }; - 55914F40E7C5BD463245E2016A8906BC /* JXSegmentedTitleImageCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleImageCell.swift; path = Sources/TitleImage/JXSegmentedTitleImageCell.swift; sourceTree = ""; }; - 55E9A15EC42E241A39E5CABDB5E0D428 /* Zones.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Zones.swift; path = Sources/SwiftDate/Supports/Zones.swift; sourceTree = ""; }; - 56263B06F3EAEC382F6F91F1599D4E74 /* Alamofire-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-umbrella.h"; sourceTree = ""; }; - 5699EE5AE55BF8C2FE30604FAED64A7C /* HTTPHeaders.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPHeaders.swift; path = Source/Core/HTTPHeaders.swift; sourceTree = ""; }; - 56CF6C9E5C4416011FAC32CEB31AA525 /* Box.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Box.swift; path = Sources/Utility/Box.swift; sourceTree = ""; }; - 56E8C8A30A6C29843067AEFDBDB0DD5E /* IQUITextFieldView+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUITextFieldView+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift"; sourceTree = ""; }; - 570B90AE83D4F878D222EF7D7B7A18F9 /* ConstraintDirectionalInsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDirectionalInsetTarget.swift; path = Sources/ConstraintDirectionalInsetTarget.swift; sourceTree = ""; }; - 58092F279DEDF69EA7576C313DC62F89 /* ConstraintItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintItem.swift; path = Sources/ConstraintItem.swift; sourceTree = ""; }; + 2F6348E3B982F1C6A8183DE435D49C1E /* DateInRegion+Math.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Math.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Math.swift"; sourceTree = ""; }; + 300F57DE95241E7D9CB929D3B2264440 /* KFAnimatedImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFAnimatedImage.swift; path = Sources/SwiftUI/KFAnimatedImage.swift; sourceTree = ""; }; + 306B8810C66C0963989D2FF077E79536 /* Formatter+Protocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Formatter+Protocols.swift"; path = "Sources/SwiftDate/Formatters/Formatter+Protocols.swift"; sourceTree = ""; }; + 30E74D845B956ED13AF023CCFCFB780E /* CallbackQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CallbackQueue.swift; path = Sources/Utility/CallbackQueue.swift; sourceTree = ""; }; + 316F9447AB3AC7D8F275685D9B863F2B /* SwiftDate-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftDate-prefix.pch"; sourceTree = ""; }; + 319CEE88A0DEACE6042548814209CC59 /* WKInterfaceImage+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "WKInterfaceImage+Kingfisher.swift"; path = "Sources/Extensions/WKInterfaceImage+Kingfisher.swift"; sourceTree = ""; }; + 322678B5A6885D82212954266BD74117 /* SVProgressHUD-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SVProgressHUD-dummy.m"; sourceTree = ""; }; + 359F20447DD6B2DABE3B77D75DA92F82 /* FreeStreamer */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FreeStreamer; path = FreeStreamer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 367AAD6A6017F88B42A968FCBA8F44A8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = MJRefresh/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 379586CA36717B53DDF60E7C554E84C3 /* MJRefreshConst.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshConst.h; path = MJRefresh/MJRefreshConst.h; sourceTree = ""; }; + 37EB4D3AA3806AA06D95FA6341AFE44A /* JXSegmentedIndicatorBackgroundView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorBackgroundView.swift; path = Sources/Indicator/JXSegmentedIndicatorBackgroundView.swift; sourceTree = ""; }; + 382BC06430FA8EEAEE68E10A5501E0ED /* MJRefreshHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshHeader.h; path = MJRefresh/Base/MJRefreshHeader.h; sourceTree = ""; }; + 38A6048835ACC3364C18CAD1AF32230E /* DateRepresentable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateRepresentable.swift; path = Sources/SwiftDate/DateRepresentable.swift; sourceTree = ""; }; + 38AA948D61BC9D3D35F9604E5A3BC412 /* ConstraintLayoutSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutSupport.swift; path = Sources/ConstraintLayoutSupport.swift; sourceTree = ""; }; + 38D414568B081DC66E575280B0C9F52A /* JXSegmentedAnimator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedAnimator.swift; path = Sources/Common/JXSegmentedAnimator.swift; sourceTree = ""; }; + 3C1FD60AF28AF4C21BAC2E0EF16B4A52 /* stream_configuration.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = stream_configuration.cpp; path = FreeStreamer/FreeStreamer/stream_configuration.cpp; sourceTree = ""; }; + 3C5D894D9E7403FED9BC0BC7102A3429 /* JXSegmentedView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedView.swift; path = Sources/Core/JXSegmentedView.swift; sourceTree = ""; }; + 3C805EF5F028E6D4F8C47BE994E7975B /* MJRefreshBackGifFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackGifFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.m; sourceTree = ""; }; + 3DB06C2DEEDF5BE52555F1C882B3020E /* JXSegmentedTitleDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleDataSource.swift; path = Sources/Title/JXSegmentedTitleDataSource.swift; sourceTree = ""; }; + 3DDA2472F9717357C6BB8F71997F4BB1 /* Int+DateComponents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Int+DateComponents.swift"; path = "Sources/SwiftDate/Foundation+Extras/Int+DateComponents.swift"; sourceTree = ""; }; + 3DF55BDFE48613A40A1EF055E76F6481 /* IQKeyboardManagerConstants.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManagerConstants.swift; path = IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift; sourceTree = ""; }; + 3DFD149EDF95FEEEF188C759F64C9387 /* FreeStreamer.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FreeStreamer.modulemap; sourceTree = ""; }; + 3DFFF177D8E56A2AA8900BF72F3D7B10 /* IQKeyboardManagerSwift-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IQKeyboardManagerSwift-prefix.pch"; sourceTree = ""; }; + 3F4510AFAF76845CC75D6531D2688FC8 /* JXPagingView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXPagingView-prefix.pch"; sourceTree = ""; }; + 3F496457565E077ADD0E21CF6E432F08 /* SVIndefiniteAnimatedView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVIndefiniteAnimatedView.m; path = SVProgressHUD/SVIndefiniteAnimatedView.m; sourceTree = ""; }; + 3FB693B14BC2F5BB232B918BC82C24C8 /* TimePeriodGroup.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodGroup.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodGroup.swift; sourceTree = ""; }; + 3FC20D0CF5D2FFA24D77CA142F23498C /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Source/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 3FF6EFC03B1FAE3BFFE25B327C58E984 /* Box.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Box.swift; path = Sources/Utility/Box.swift; sourceTree = ""; }; + 400FF55D0451E7A8F33A3D0D3E11C1B9 /* Reachability */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Reachability; path = Reachability.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 403E083CF449B4B0144BC769B97043DC /* UIView+MJExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+MJExtension.m"; path = "MJRefresh/UIView+MJExtension.m"; sourceTree = ""; }; + 40A5DBCBB01B810B9E309C9CF63DD609 /* KingfisherError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherError.swift; path = Sources/General/KingfisherError.swift; sourceTree = ""; }; + 40AD76C863D0635308D9E71629E36156 /* TimeStructures.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimeStructures.swift; path = Sources/SwiftDate/Supports/TimeStructures.swift; sourceTree = ""; }; + 416332D3DF3451F4A442AC1248F5BEB2 /* KFImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImage.swift; path = Sources/SwiftUI/KFImage.swift; sourceTree = ""; }; + 4201EF3E35074620F075BCE228F7AEE5 /* MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefresh.h; path = MJRefresh/MJRefresh.h; sourceTree = ""; }; + 428DE06C7C62F2DF10FDF5B1C9C09F90 /* SVProgressHUD.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SVProgressHUD.release.xcconfig; sourceTree = ""; }; + 42E0088C30E4A370DC59A6329299BF5F /* UILayoutSupport+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UILayoutSupport+Extensions.swift"; path = "Sources/UILayoutSupport+Extensions.swift"; sourceTree = ""; }; + 43CC9FECC112EC531F730A6997A7AA46 /* Region.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Region.swift; path = Sources/SwiftDate/DateInRegion/Region.swift; sourceTree = ""; }; + 446631A716250A41D285C5A69C889E4A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Framework/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 447339956CEEAB15D60D70501BFCF606 /* MJRefresh-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "MJRefresh-Info.plist"; sourceTree = ""; }; + 453A74195009C301A029E95CC589FB2F /* TimePeriodProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodProtocol.swift; path = Sources/SwiftDate/TimePeriod/TimePeriodProtocol.swift; sourceTree = ""; }; + 453C24702999872F6BF14260FBE3A719 /* SVProgressHUD-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-prefix.pch"; sourceTree = ""; }; + 454DD3800F47E80B70947B50B5EA6B4A /* MemoryStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MemoryStorage.swift; path = Sources/Cache/MemoryStorage.swift; sourceTree = ""; }; + 45AD14BC628E6E28901E62CB0D86DFDA /* SVProgressHUD-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SVProgressHUD-Info.plist"; sourceTree = ""; }; + 45FF34A749D6457211D8DAA92C371E13 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; + 4846DC539D38816DBC96DB7BF50FF1CE /* IQPlaceholderable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQPlaceholderable.swift; path = IQKeyboardManagerSwift/IQTextView/IQPlaceholderable.swift; sourceTree = ""; }; + 488856EC78D9E7B766DB9E061CABC8F5 /* WebSocketRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = WebSocketRequest.swift; path = Source/Core/WebSocketRequest.swift; sourceTree = ""; }; + 48CA236C8CAAD1ED04F663C2CD8B0BB4 /* Reachability-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Reachability-prefix.pch"; sourceTree = ""; }; + 48F66D551A5D9A040F61CB55CE4CBC72 /* FreeStreamer-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FreeStreamer-Info.plist"; sourceTree = ""; }; + 49619FA43727E6B05EFF845F078B07FF /* Placeholder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Placeholder.swift; path = Sources/Image/Placeholder.swift; sourceTree = ""; }; + 4975EAB4702DC92577C2ADE5E71D1FBA /* SVProgressAnimatedView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVProgressAnimatedView.h; path = SVProgressHUD/SVProgressAnimatedView.h; sourceTree = ""; }; + 4A7C6DA07E0232526BB718A5A28B5C3F /* JXSegmentedDotCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedDotCell.swift; path = Sources/Dot/JXSegmentedDotCell.swift; sourceTree = ""; }; + 4AEBA9C1E1A64D0D2E56C9ECA1009355 /* KFOptionsSetter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFOptionsSetter.swift; path = Sources/General/KFOptionsSetter.swift; sourceTree = ""; }; + 4BB2313FBFE241B878F16C54B58F931A /* Kingfisher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-umbrella.h"; sourceTree = ""; }; + 4C9A86C2DA499569E7EB235FF55769B3 /* TimePeriod.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriod.swift; path = Sources/SwiftDate/TimePeriod/TimePeriod.swift; sourceTree = ""; }; + 4DBABAD38468BD2235D1345DAFA6B8FF /* FreeStreamer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FreeStreamer.release.xcconfig; sourceTree = ""; }; + 4F09B7EC32CFE88B4A26B887D4166EAE /* MJRefreshNormalTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshNormalTrailer.m; path = MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.m; sourceTree = ""; }; + 4F46CEA011DB42369F3F660437B779AF /* ResourceBundle-Reachability_Privacy-Reachability-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Reachability_Privacy-Reachability-Info.plist"; sourceTree = ""; }; + 50068CE99A1D5B57ADA44E975763524F /* ConstraintMultiplierTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMultiplierTarget.swift; path = Sources/ConstraintMultiplierTarget.swift; sourceTree = ""; }; + 505ACEC8786DA864009CAFAD8D7ACC74 /* JXSegmentedTitleDynamicConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleDynamicConfiguration.swift; path = Sources/Title/JXSegmentedTitleDynamicConfiguration.swift; sourceTree = ""; }; + 5095BA006903F202D549CE2B34B14AA2 /* MJRefreshFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshFooter.m; path = MJRefresh/Base/MJRefreshFooter.m; sourceTree = ""; }; + 50C5A927AB7AF95FB88E19C10BB2C621 /* MJRefreshStateHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshStateHeader.m; path = MJRefresh/Custom/Header/MJRefreshStateHeader.m; sourceTree = ""; }; + 51197F733B8466F86DCAA59DB57532F1 /* ConstraintMaker.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMaker.swift; path = Sources/ConstraintMaker.swift; sourceTree = ""; }; + 5140A9A1F5487CA7CF613C7220D594D3 /* ImageDrawing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDrawing.swift; path = Sources/Image/ImageDrawing.swift; sourceTree = ""; }; + 5142C85EC3B111E46F791033767E12E2 /* Alamofire-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-umbrella.h"; sourceTree = ""; }; + 51D0D0C744FF259ADDA859BD13CB68F5 /* ConstraintLayoutGuideDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutGuideDSL.swift; path = Sources/ConstraintLayoutGuideDSL.swift; sourceTree = ""; }; + 522D18990BF9254D37574205E2FE8309 /* Tiercel-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Tiercel-Info.plist"; sourceTree = ""; }; + 52F8DFC026AC5D16DD4867C898032787 /* ConstraintRelatableTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintRelatableTarget.swift; path = Sources/ConstraintRelatableTarget.swift; sourceTree = ""; }; + 532AAD5D5F6498A0595431E5809B9229 /* RequestInterceptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestInterceptor.swift; path = Source/Features/RequestInterceptor.swift; sourceTree = ""; }; + 53FD870D570E7836D8D696AFA899C174 /* MJRefreshBackGifFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackGifFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.h; sourceTree = ""; }; + 54020DD80FFCE6C88B44AD8F555783A9 /* ConstraintConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintConfig.swift; path = Sources/ConstraintConfig.swift; sourceTree = ""; }; + 543DBF6743B34366D2E9B3D55471D3AF /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; + 54B4FA920379EF013B60907CCBEA03F8 /* SVRadialGradientLayer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVRadialGradientLayer.h; path = SVProgressHUD/SVRadialGradientLayer.h; sourceTree = ""; }; + 54BC69F7BD5CCB745F0FB103B3478E38 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 55061A053D3068F4AD9F6B8884C86A1B /* LayoutConstraintItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayoutConstraintItem.swift; path = Sources/LayoutConstraintItem.swift; sourceTree = ""; }; + 558ADE5127C2037C1373813C4E4DA1D6 /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Source/Features/MultipartFormData.swift; sourceTree = ""; }; + 55BEC05992BEE08743A5630C5E36E343 /* ConstraintViewDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintViewDSL.swift; path = Sources/ConstraintViewDSL.swift; sourceTree = ""; }; + 56D2D30775656A6F7F644EC3E77AC357 /* SessionDataTask.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDataTask.swift; path = Sources/Networking/SessionDataTask.swift; sourceTree = ""; }; + 56F310A7F250FAC36DFA8E59F573C957 /* ConstraintLayoutGuide+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintLayoutGuide+Extensions.swift"; path = "Sources/ConstraintLayoutGuide+Extensions.swift"; sourceTree = ""; }; + 58058F4BB7020039FDF85930D314381A /* Tiercel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Tiercel.debug.xcconfig; sourceTree = ""; }; 58AE0544E0C381DDBD09356C357EC82B /* SwiftDate */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SwiftDate; path = SwiftDate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 594D2794764B5796C8A422C4401A7E2F /* Date+Create.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Create.swift"; path = "Sources/SwiftDate/Date/Date+Create.swift"; sourceTree = ""; }; - 5AA5BD68BC8AE62D30B207A9A089DBE2 /* IQToolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQToolbar.swift; path = IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift; sourceTree = ""; }; - 5AAF19FA9EB514B2794ECCAD1BB7FA2E /* Filter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Sources/Image/Filter.swift; sourceTree = ""; }; - 5AB27763B449AF051985734B3773B4ED /* TimePeriodCollection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodCollection.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodCollection.swift; sourceTree = ""; }; - 5AEFD27CAFBA0B3BB80B6F22588271D1 /* MJRefresh-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "MJRefresh-Info.plist"; sourceTree = ""; }; - 5B832310B3844FB85DE10E60B45F44A3 /* SwiftDate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftDate.swift; path = Sources/SwiftDate/SwiftDate.swift; sourceTree = ""; }; - 5CEFF5B6E9210A0DDADD6C198F0BC68C /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Alamofire.modulemap; sourceTree = ""; }; - 5D15BEF86A67CAF30B602BB5A260F77B /* KFAnimatedImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFAnimatedImage.swift; path = Sources/SwiftUI/KFAnimatedImage.swift; sourceTree = ""; }; - 5D36A80F29E20B609E647BAFB548A270 /* ImageDownloaderDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloaderDelegate.swift; path = Sources/Networking/ImageDownloaderDelegate.swift; sourceTree = ""; }; + 5984A98BF9B928B0D0AB3A27CF8809CE /* id3_parser.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = id3_parser.cpp; path = FreeStreamer/FreeStreamer/id3_parser.cpp; sourceTree = ""; }; + 5A1C920032B029FBF2CB4D3E422174E9 /* RetryPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RetryPolicy.swift; path = Source/Features/RetryPolicy.swift; sourceTree = ""; }; + 5C43593ADD566299B8A9B4AC5DD8EBB0 /* JXSegmentedNumberCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedNumberCell.swift; path = Sources/Number/JXSegmentedNumberCell.swift; sourceTree = ""; }; + 5C9F75C6ADBAE1B1E97DFE22EBDDCDFD /* UIScrollView+MJExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+MJExtension.m"; path = "MJRefresh/UIScrollView+MJExtension.m"; sourceTree = ""; }; + 5D010AF7D78D6C152BA967EB3141BFF7 /* MJRefreshGifHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshGifHeader.m; path = MJRefresh/Custom/Header/MJRefreshGifHeader.m; sourceTree = ""; }; 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Alamofire; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5DD688DF2ADF79B72ECC735C35F05F41 /* MJRefresh-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "MJRefresh-dummy.m"; sourceTree = ""; }; - 5EBCD15895605FBBBCD96B9C8E928D3B /* Alamofire.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.debug.xcconfig; sourceTree = ""; }; - 612886EAB1AE5139D8545245115B7EC8 /* MJRefreshHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshHeader.m; path = MJRefresh/Base/MJRefreshHeader.m; sourceTree = ""; }; - 61585200C65A159F5B6731050D64BA59 /* SessionDataTask.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDataTask.swift; path = Sources/Networking/SessionDataTask.swift; sourceTree = ""; }; - 65BAE3F45733F6EB3DAE2F48599D8CAB /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Source/Core/SessionDelegate.swift; sourceTree = ""; }; - 65E17F9E27F1D47AFFD1AE176DFE1657 /* ConstraintOffsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintOffsetTarget.swift; path = Sources/ConstraintOffsetTarget.swift; sourceTree = ""; }; - 662BA990AF2EEE7892AF8EA530C1AE31 /* ImageProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProcessor.swift; path = Sources/Image/ImageProcessor.swift; sourceTree = ""; }; - 667B5BDDACE02078C87EFED6AAB723C2 /* ConstraintPriorityTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintPriorityTarget.swift; path = Sources/ConstraintPriorityTarget.swift; sourceTree = ""; }; - 67318C8CDF97D3763D84C0B08910AD54 /* JXSegmentedView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedView.swift; path = Sources/Core/JXSegmentedView.swift; sourceTree = ""; }; - 67675B8E7AD45A266A61E66355512D15 /* TVMonogramView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TVMonogramView+Kingfisher.swift"; path = "Sources/Extensions/TVMonogramView+Kingfisher.swift"; sourceTree = ""; }; - 67853DE27314756744C79CE55FBAA9A4 /* MJRefreshBackStateFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackStateFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.h; sourceTree = ""; }; - 6855310DB35D41B1E466E5EFFAF771BE /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Source/Features/RedirectHandler.swift; sourceTree = ""; }; - 68A219042E44DB63D69EE3B79763130C /* Source.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Source.swift; path = Sources/General/ImageSource/Source.swift; sourceTree = ""; }; - 695AA5A7D6BF0B59DFBF71ABC3F3FECE /* JXSegmentedTitleGradientDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleGradientDataSource.swift; path = Sources/TitleGradient/JXSegmentedTitleGradientDataSource.swift; sourceTree = ""; }; - 6AC9CFB040D783BBBD3E787C142BFF2A /* JXSegmentedIndicatorGradientLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorGradientLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorGradientLineView.swift; sourceTree = ""; }; - 6ADEBF08B3C1426137C7A2ED9E5E54D8 /* ConstraintLayoutSupportDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutSupportDSL.swift; path = Sources/ConstraintLayoutSupportDSL.swift; sourceTree = ""; }; - 6AFFBD2618F9F069B2DFFC4829E58831 /* ConstraintLayoutSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutSupport.swift; path = Sources/ConstraintLayoutSupport.swift; sourceTree = ""; }; - 6B28820A2229C358F6F00F9A6EE1A659 /* JXSegmentedTitleAttributeDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleAttributeDataSource.swift; path = Sources/AttributeTitle/JXSegmentedTitleAttributeDataSource.swift; sourceTree = ""; }; - 6B4DCC2536CD7F8BD55F0BAA08F110C7 /* ConstraintLayoutGuide+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintLayoutGuide+Extensions.swift"; path = "Sources/ConstraintLayoutGuide+Extensions.swift"; sourceTree = ""; }; - 6B82834CA67E7FF1B8610BF181E51004 /* SVProgressHUD.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SVProgressHUD.release.xcconfig; sourceTree = ""; }; - 6BF75314341E15EAF5EC8A5095F2878D /* MemoryStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MemoryStorage.swift; path = Sources/Cache/MemoryStorage.swift; sourceTree = ""; }; - 6C5F4CF175A8588A2773B4AF89EA51EC /* MJRefreshAutoNormalFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoNormalFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.h; sourceTree = ""; }; - 6CEA2AD00C187BA485EFF154E41EF587 /* ImageFormat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageFormat.swift; path = Sources/Image/ImageFormat.swift; sourceTree = ""; }; - 6D3E36D2866A136708CF5F602D268BD7 /* HTTPMethod.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPMethod.swift; path = Source/Core/HTTPMethod.swift; sourceTree = ""; }; - 6D65D8CBF9D74AB85CB305B6D9B87509 /* DiskStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DiskStorage.swift; path = Sources/Cache/DiskStorage.swift; sourceTree = ""; }; - 6E1CCD164ECD3A398893DB0C3F224494 /* CallbackQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CallbackQueue.swift; path = Sources/Utility/CallbackQueue.swift; sourceTree = ""; }; - 6E619DA6F3D601658833035BDA0E167C /* Commons.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Commons.swift; path = Sources/SwiftDate/Supports/Commons.swift; sourceTree = ""; }; - 6E6DA59208C9C680C1A08AB6B751A05E /* RequestCompression.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestCompression.swift; path = Source/Features/RequestCompression.swift; sourceTree = ""; }; - 6EF651FECE01069367CDE0054F13249F /* Formatter+Protocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Formatter+Protocols.swift"; path = "Sources/SwiftDate/Formatters/Formatter+Protocols.swift"; sourceTree = ""; }; - 6F69248BB24307C449A27A28477BAF56 /* NSTextAttachment+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSTextAttachment+Kingfisher.swift"; path = "Sources/Extensions/NSTextAttachment+Kingfisher.swift"; sourceTree = ""; }; - 6F86EAA1D247756258692097BEA95A21 /* JXSegmentedTitleCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleCell.swift; path = Sources/Title/JXSegmentedTitleCell.swift; sourceTree = ""; }; - 7052242B5B59DCEC7125EE873BC0202A /* AuthenticationInterceptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AuthenticationInterceptor.swift; path = Source/Features/AuthenticationInterceptor.swift; sourceTree = ""; }; - 70D291797444B210FC11B05604380747 /* MJRefreshGifHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshGifHeader.m; path = MJRefresh/Custom/Header/MJRefreshGifHeader.m; sourceTree = ""; }; - 7143F3A6986BF2AB2828E2DC37C9C09D /* String+MD5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+MD5.swift"; path = "Sources/Utility/String+MD5.swift"; sourceTree = ""; }; - 71DCFBED49883055AA94888206F221A9 /* JXSegmentedIndicatorRainbowLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorRainbowLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorRainbowLineView.swift; sourceTree = ""; }; - 725764D6EDA104D9902913F6D02AB7FF /* TimePeriod+Support.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TimePeriod+Support.swift"; path = "Sources/SwiftDate/TimePeriod/TimePeriod+Support.swift"; sourceTree = ""; }; - 728D0FC1713F8BD85AD5D78DF7CC3E06 /* RelativeFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RelativeFormatter.swift; path = Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter.swift; sourceTree = ""; }; - 7477826A60436064D61A5EFA564B9D6F /* ConstraintRelation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintRelation.swift; path = Sources/ConstraintRelation.swift; sourceTree = ""; }; - 7682AD6DBFD519C9BA5ECCAC483C0CF4 /* ConstraintInsets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintInsets.swift; path = Sources/ConstraintInsets.swift; sourceTree = ""; }; - 7694CB3A399B0E568F03F27D28868B95 /* DisplayLink.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DisplayLink.swift; path = Sources/Utility/DisplayLink.swift; sourceTree = ""; }; - 76BE0C86A979CEA47E0B7AD2EE15D888 /* NSBundle+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSBundle+MJRefresh.m"; path = "MJRefresh/NSBundle+MJRefresh.m"; sourceTree = ""; }; - 773DD03EC2EC00F685DE0E46779320CC /* IQKeyboardManager+Internal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Internal.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Internal.swift"; sourceTree = ""; }; - 774F6E017AC64D4F20E0DA360CA3FECF /* Placeholder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Placeholder.swift; path = Sources/Image/Placeholder.swift; sourceTree = ""; }; - 77AF4A7E7C61F4843CEE9608EFB1F2A8 /* SnapKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapKit-prefix.pch"; sourceTree = ""; }; - 7A43EE3F113EE7EB81E5065DB5FDD3F2 /* MJRefreshAutoFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoFooter.m; path = MJRefresh/Base/MJRefreshAutoFooter.m; sourceTree = ""; }; - 7A78C7902D8016F72AC215D2E28422F6 /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Sources/Networking/RedirectHandler.swift; sourceTree = ""; }; - 7B24959E1B5D6E0DB944451D77D9BEEF /* JXSegmentedView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "JXSegmentedView-Info.plist"; sourceTree = ""; }; - 7B9379FA3746B5215A31DE8D1CD26166 /* MJRefreshAutoStateFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoStateFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.h; sourceTree = ""; }; - 7BD8D5C9444C4C58CF152CD4CC8DB0A3 /* Alamofire.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.release.xcconfig; sourceTree = ""; }; - 7C1665911416D598AAD937FC0E0D42E4 /* RequestTaskMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestTaskMap.swift; path = Source/Core/RequestTaskMap.swift; sourceTree = ""; }; - 7C1E1CFDA9E2D6AECCA91F210C0B6064 /* IQNSArray+Sort.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQNSArray+Sort.swift"; path = "IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift"; sourceTree = ""; }; - 7C589304DE45B301793ACB48B3D74C25 /* SVProgressHUD.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVProgressHUD.h; path = SVProgressHUD/SVProgressHUD.h; sourceTree = ""; }; + 5F732A9C653F736C5918C2BE6C341FA4 /* KFImageOptions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImageOptions.swift; path = Sources/SwiftUI/KFImageOptions.swift; sourceTree = ""; }; + 5FE20AE5C1EF7F685ADC6C4B3A255B06 /* SVProgressHUD-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-umbrella.h"; sourceTree = ""; }; + 60917B37B15D85EBE318A3B375752857 /* SnapKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SnapKit.debug.xcconfig; sourceTree = ""; }; + 60F0C37A6C30DB306561499EA4E6BCDC /* JXSegmentedTitleAttributeDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleAttributeDataSource.swift; path = Sources/AttributeTitle/JXSegmentedTitleAttributeDataSource.swift; sourceTree = ""; }; + 6265DB8E8D0BAED2C39405AECD61AF04 /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Sources/General/SessionDelegate.swift; sourceTree = ""; }; + 63D0F1F07C36562816CB8E6D41FBDB3E /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + 64506C7AE6F75CC4AB585A017D14BE73 /* String+MD5.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+MD5.swift"; path = "Sources/Utility/String+MD5.swift"; sourceTree = ""; }; + 64647B22D390B0085715A7A6C6432D74 /* URLConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLConvertible.swift; path = Sources/General/URLConvertible.swift; sourceTree = ""; }; + 646CA35D7A677037E651FB1DCAF038DA /* Protected.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Protected.swift; path = Source/Core/Protected.swift; sourceTree = ""; }; + 6473EC965BEEC93B5FEB22E0B16F7E19 /* file_output.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = file_output.cpp; path = FreeStreamer/FreeStreamer/file_output.cpp; sourceTree = ""; }; + 648A2050353718943995F249C37376B6 /* SwiftDate.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftDate.release.xcconfig; sourceTree = ""; }; + 6490D638BB8DD75279D3868652896488 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; + 65814C78FFC0F5199CE49EAC7BFD63EF /* IQToolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQToolbar.swift; path = IQKeyboardManagerSwift/IQToolbar/IQToolbar.swift; sourceTree = ""; }; + 65AFBD9AEA9E99F7AC7C95712E97940F /* SwiftDate-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SwiftDate-Info.plist"; sourceTree = ""; }; + 66BC4906D86D7379576ACE241C5C5B97 /* IQKeyboardManagerSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IQKeyboardManagerSwift.release.xcconfig; sourceTree = ""; }; + 67D4BE2E3AB761AADDBACFB7F2B3FC96 /* RelativeFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RelativeFormatter.swift; path = Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter.swift; sourceTree = ""; }; + 6852392DB22126B984A79E66779323DD /* audio_stream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = audio_stream.h; path = FreeStreamer/FreeStreamer/audio_stream.h; sourceTree = ""; }; + 68C6AC662AF5F96BCF9E223D29845982 /* Kingfisher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Kingfisher-dummy.m"; sourceTree = ""; }; + 68F6612E77D997F33CF6B5B66DC48B2D /* MJRefreshStateTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshStateTrailer.m; path = MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m; sourceTree = ""; }; + 69708C606EE0513511D8D46B93793A9D /* IQUITextFieldView+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUITextFieldView+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUITextFieldView+Additions.swift"; sourceTree = ""; }; + 69A60B500F2A28EF683EAB65A6568396 /* stream_configuration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = stream_configuration.h; path = FreeStreamer/FreeStreamer/stream_configuration.h; sourceTree = ""; }; + 6A0E8D36BBC515C74EBC7794AC59F2B0 /* Alamofire.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Alamofire.modulemap; sourceTree = ""; }; + 6A7890939418A54F31B5E2A8545372FD /* IQKeyboardManager+UIKeyboardNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+UIKeyboardNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+UIKeyboardNotification.swift"; sourceTree = ""; }; + 6AE6589CF9C3283A65A9CCFB4A863A16 /* ConstraintMakerEditable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerEditable.swift; path = Sources/ConstraintMakerEditable.swift; sourceTree = ""; }; + 6B6FA226D0E99F388099E2762F2A0DB0 /* SnapKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapKit-prefix.pch"; sourceTree = ""; }; + 6BEB2A0F222387786538A58D8C49C93A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 6C3B19C6050B49E5E5B56765AEF5FE5F /* Cache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Cache.swift; path = Sources/General/Cache.swift; sourceTree = ""; }; + 6EBF85EEDF18C69141A9E1E703C905CD /* IQKeyboardManagerSwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "IQKeyboardManagerSwift-dummy.m"; sourceTree = ""; }; + 6EC99E48058E2E9A34BF98B47999CEA1 /* MJRefresh-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MJRefresh-prefix.pch"; sourceTree = ""; }; + 6F15B46DEB48666B2352074A40A67706 /* FreeStreamer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FreeStreamer.debug.xcconfig; sourceTree = ""; }; + 6F835F11137C679D5CF9597B87B42CEC /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkReachabilityManager.swift; path = Source/Features/NetworkReachabilityManager.swift; sourceTree = ""; }; + 72A654D4A98DED2922D1F9F69A777034 /* JXSegmentedTitleOrImageDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleOrImageDataSource.swift; path = Sources/TitleOrImage/JXSegmentedTitleOrImageDataSource.swift; sourceTree = ""; }; + 72BB35358219022632C6A9EF8A462D1F /* JXSegmentedIndicatorDotLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorDotLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorDotLineView.swift; sourceTree = ""; }; + 72FEC0D9F81CB5F7CE5FDE9B82B17585 /* DateInRegion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateInRegion.swift; path = Sources/SwiftDate/DateInRegion/DateInRegion.swift; sourceTree = ""; }; + 73116E04CFC77FBAC5754FB3135DD50F /* JXSegmentedView-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "JXSegmentedView-dummy.m"; sourceTree = ""; }; + 73263B2692F575379BC2971651456D08 /* Combine.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Combine.swift; path = Source/Features/Combine.swift; sourceTree = ""; }; + 740B2DF7CACF2EE62202C33B3853A83C /* JXSegmentedTitleAttributeCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleAttributeCell.swift; path = Sources/AttributeTitle/JXSegmentedTitleAttributeCell.swift; sourceTree = ""; }; + 7469843EF11B7651EAA26E55E112E349 /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Sources/Utility/Result.swift; sourceTree = ""; }; + 760BA463607D8ACAEEA23CB221E8880E /* JXSegmentedIndicatorParams.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorParams.swift; path = Sources/Indicator/JXSegmentedIndicatorParams.swift; sourceTree = ""; }; + 762E686A23E3C05BFD37C98D4CBA9424 /* TimePeriodChain.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodChain.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodChain.swift; sourceTree = ""; }; + 76603E85B6156E967FC2F4A6E2651206 /* DataRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataRequest.swift; path = Source/Core/DataRequest.swift; sourceTree = ""; }; + 77C16D3C7CB62A673F650B8575E7221D /* Session.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Session.swift; path = Source/Core/Session.swift; sourceTree = ""; }; + 77DB14F123949BD939D3F699B4266D69 /* SnapKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapKit-umbrella.h"; sourceTree = ""; }; + 77EFAEF82C69A4F5F14C4CDB669C9D9C /* UICollectionViewLayout+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UICollectionViewLayout+MJRefresh.h"; path = "MJRefresh/UICollectionViewLayout+MJRefresh.h"; sourceTree = ""; }; + 78C8DF2C971F4CFCCC05A727B2DE3B61 /* Kingfisher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-prefix.pch"; sourceTree = ""; }; + 78E348898C88C370F77ED61FD69CB79C /* UIView+MJExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+MJExtension.h"; path = "MJRefresh/UIView+MJExtension.h"; sourceTree = ""; }; + 7A0F024C688B9208EE21BA6A338CAF16 /* Executer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Executer.swift; path = Sources/General/Executer.swift; sourceTree = ""; }; + 7ABAC09EFA34B7B44CD9B563DD0D4C54 /* JXPagingMainTableView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingMainTableView.swift; path = Sources/JXPagingView/JXPagingMainTableView.swift; sourceTree = ""; }; + 7B4870DA3D6F556BFAD1D0FD607F3F53 /* TimePeriod+Support.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TimePeriod+Support.swift"; path = "Sources/SwiftDate/TimePeriod/TimePeriod+Support.swift"; sourceTree = ""; }; + 7CB1AF83C65AAD9D529E0E732D2C71B8 /* MJRefreshNormalHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshNormalHeader.h; path = MJRefresh/Custom/Header/MJRefreshNormalHeader.h; sourceTree = ""; }; + 7E1B99970D923DF6228679CB6A465895 /* JXSegmentedTitleImageCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleImageCell.swift; path = Sources/TitleImage/JXSegmentedTitleImageCell.swift; sourceTree = ""; }; 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "MJRefresh-MJRefresh.Privacy"; path = MJRefresh.Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E3E285614C475158E65C94AA0A728CD /* CodingUserInfoKey+Cache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "CodingUserInfoKey+Cache.swift"; path = "Sources/Extensions/CodingUserInfoKey+Cache.swift"; sourceTree = ""; }; 7EB20B4E68CCB69F85E7D08B7F8463D6 /* JXPagingView-JXPagingView */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "JXPagingView-JXPagingView"; path = JXPagingView.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - 7EB8A3AF649521A0ED645B8858FB4DDF /* JXSegmentedViewTool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedViewTool.swift; path = Sources/Common/JXSegmentedViewTool.swift; sourceTree = ""; }; - 7EC53E7C517308EABC44AEFB75F5C064 /* ConstraintMakerExtendable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerExtendable.swift; path = Sources/ConstraintMakerExtendable.swift; sourceTree = ""; }; - 7ECC357045F3ADF02DFCA0E0F6D4DEE0 /* TimePeriod.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriod.swift; path = Sources/SwiftDate/TimePeriod/TimePeriod.swift; sourceTree = ""; }; - 7FBC7116157221928733CE2DB074EB86 /* ConstraintView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintView.swift; path = Sources/ConstraintView.swift; sourceTree = ""; }; - 810BB8FC42B516FF79349CC6C8C13E4C /* UIView+MJExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+MJExtension.h"; path = "MJRefresh/UIView+MJExtension.h"; sourceTree = ""; }; - 8124055AC9E04813E53A3EE6EDD5E1DE /* MJRefreshHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshHeader.h; path = MJRefresh/Base/MJRefreshHeader.h; sourceTree = ""; }; - 8138B9E4A2A6AA8C683562DF69844549 /* AnimatedImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedImageView.swift; path = Sources/Views/AnimatedImageView.swift; sourceTree = ""; }; - 813F5F884BA0F0F6C062E545B4CA81EC /* MJRefreshConfig.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshConfig.m; path = MJRefresh/MJRefreshConfig.m; sourceTree = ""; }; - 814205A97B8158339AF6DCF93609360F /* Kingfisher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-umbrella.h"; sourceTree = ""; }; - 81D659C29A42861AC5A4EF3B9D26DC0B /* IQTitleBarButtonItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQTitleBarButtonItem.swift; path = IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift; sourceTree = ""; }; - 828F1C480D9959D94E2868CE1084677F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; - 82CD43FF64DC36B945840368DAC1706E /* MJRefreshStateHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshStateHeader.m; path = MJRefresh/Custom/Header/MJRefreshStateHeader.m; sourceTree = ""; }; - 82E922355D2129F96245BA8E4E5FFF67 /* MJRefresh-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MJRefresh-umbrella.h"; sourceTree = ""; }; - 832AF59B8B94B734BFA5598CF822D2E8 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Source/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 835F2888E352852912F4709F5E54512B /* JXSegmentedIndicatorDoubleLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorDoubleLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorDoubleLineView.swift; sourceTree = ""; }; - 83C7B6631D6A0479168A89D25B68A47C /* Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Kingfisher.swift; path = Sources/General/Kingfisher.swift; sourceTree = ""; }; - 84C0F797B07A4C95A5F90FB6C7E6A296 /* SizeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SizeExtensions.swift; path = Sources/Utility/SizeExtensions.swift; sourceTree = ""; }; - 84C32F4F34E89EC16281BE7CB8022B54 /* Alamofire-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Alamofire-prefix.pch"; sourceTree = ""; }; - 84DF245C6F43F9612AD56BC7D2ADB123 /* String+Parser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+Parser.swift"; path = "Sources/SwiftDate/Foundation+Extras/String+Parser.swift"; sourceTree = ""; }; - 84E085540B9A46FE188C3ED65522C329 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 84FCB0C95F94873F8D87374CE1ACA147 /* MJRefresh.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = MJRefresh.bundle; path = MJRefresh/MJRefresh.bundle; sourceTree = ""; }; - 86134D091916FFC476610F2EBFE771E2 /* ISOParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISOParser.swift; path = Sources/SwiftDate/Formatters/ISOParser.swift; sourceTree = ""; }; - 864DAEC4F579E88712B7F5B447424FFC /* KingfisherError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherError.swift; path = Sources/General/KingfisherError.swift; sourceTree = ""; }; - 86545830194573BF57878FC631A90181 /* MJRefreshStateHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshStateHeader.h; path = MJRefresh/Custom/Header/MJRefreshStateHeader.h; sourceTree = ""; }; - 86D05EEFA439B19ACFEC22979C2BBF4D /* SnapKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SnapKit.modulemap; sourceTree = ""; }; - 88647C8377C8FB3AE854C55C7B81559E /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NetworkReachabilityManager.swift; path = Source/Features/NetworkReachabilityManager.swift; sourceTree = ""; }; + 7F54DC04D93E64B2082AAFB9A1F4F83F /* ConstraintMakerPrioritizable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerPrioritizable.swift; path = Sources/ConstraintMakerPrioritizable.swift; sourceTree = ""; }; + 7F813F795ABA741EA4048B038E7EC4C2 /* JXSegmentedListContainerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedListContainerView.swift; path = Sources/Common/JXSegmentedListContainerView.swift; sourceTree = ""; }; + 7FA1D7762F6269DB38290C5D3BA1B21A /* IQKeyboardManagerSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IQKeyboardManagerSwift.debug.xcconfig; sourceTree = ""; }; + 803E8B9AADE51E96A38692668BA81BEF /* MJRefreshAutoFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoFooter.m; path = MJRefresh/Base/MJRefreshAutoFooter.m; sourceTree = ""; }; + 80E6C96CBB660219F7EF7C29135EF5D2 /* ImageProgressive.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProgressive.swift; path = Sources/Image/ImageProgressive.swift; sourceTree = ""; }; + 8133961A1352A59C4E8DBE54C15E2498 /* MJRefreshAutoNormalFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoNormalFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.h; sourceTree = ""; }; + 815F60113B1A64FC31D1FFBBCF1B1505 /* DateInRegion+Compare.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Compare.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Compare.swift"; sourceTree = ""; }; + 816DB2F04B71F5F810AE6A0B24C8CC71 /* MJRefreshBackFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackFooter.m; path = MJRefresh/Base/MJRefreshBackFooter.m; sourceTree = ""; }; + 819F7D2E500376ED4EE8196C862FB5BE /* JXSegmentedNumberDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedNumberDataSource.swift; path = Sources/Number/JXSegmentedNumberDataSource.swift; sourceTree = ""; }; + 81BAB32DB922253ED14C81368B9744D0 /* MJRefreshComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshComponent.m; path = MJRefresh/Base/MJRefreshComponent.m; sourceTree = ""; }; + 8200191799409C278154BB17FF45D90E /* FSPlaylistItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSPlaylistItem.h; path = FreeStreamer/FreeStreamer/FSPlaylistItem.h; sourceTree = ""; }; + 82423E826579CCC1BF9A80112E14C669 /* ISOFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISOFormatter.swift; path = Sources/SwiftDate/Formatters/ISOFormatter.swift; sourceTree = ""; }; + 82ABE0C8114FEFA9A19E7474E07C72B1 /* Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Alamofire.swift; path = Source/Alamofire.swift; sourceTree = ""; }; + 839F15386652EA3A10F991169D2BDC8F /* ConstraintMakerExtendable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerExtendable.swift; path = Sources/ConstraintMakerExtendable.swift; sourceTree = ""; }; + 84ABD14F50DD9BE7BAAB5492C0D117A8 /* AssociatedValues.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssociatedValues.swift; path = Sources/SwiftDate/Supports/AssociatedValues.swift; sourceTree = ""; }; + 84BD083CF0E74D69596F699E0B0DAC6E /* FSAudioController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSAudioController.m; path = FreeStreamer/FreeStreamer/FSAudioController.m; sourceTree = ""; }; + 84D7701F20B5E52C4FAFC9907793CFA5 /* Indicator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Indicator.swift; path = Sources/Views/Indicator.swift; sourceTree = ""; }; + 84F9D005F2B85BE22CA185718A0535FD /* MJRefreshConst.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshConst.m; path = MJRefresh/MJRefreshConst.m; sourceTree = ""; }; + 85976525DE544B30C96C2119D21D3244 /* Notifications.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Notifications.swift; path = Sources/General/Notifications.swift; sourceTree = ""; }; + 85BFAD2523C290A22C7977CF6F4A6936 /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Sources/Networking/SessionDelegate.swift; sourceTree = ""; }; + 86D907265F596152E9F2094822FF73E4 /* Task.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Task.swift; path = Sources/General/Task.swift; sourceTree = ""; }; + 873505EB51B12F5BA6246196F84D915B /* Result+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Result+Alamofire.swift"; path = "Source/Extensions/Result+Alamofire.swift"; sourceTree = ""; }; + 8754C6318F74231AB6E36C5F1BD49BDB /* RetryStrategy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RetryStrategy.swift; path = Sources/Networking/RetryStrategy.swift; sourceTree = ""; }; + 877E272496B26E87676CC7A7C6D5E26B /* Reachability-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Reachability-Info.plist"; sourceTree = ""; }; + 87B2754B6551345D199982248BF5F258 /* Delegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Delegate.swift; path = Sources/Utility/Delegate.swift; sourceTree = ""; }; + 87F341F9F0357ED1D61BA9EB641A433B /* JXSegmentedView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXSegmentedView-umbrella.h"; sourceTree = ""; }; + 87FE7DB732EAE613423384F1B5F8C6F1 /* ImageModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageModifier.swift; path = Sources/Networking/ImageModifier.swift; sourceTree = ""; }; + 88000352748C51EE5F5C9F5B89E199A9 /* IQKeyboardManager+Internal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Internal.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Internal.swift"; sourceTree = ""; }; + 881A67EADDDFC811B6A54CC57595D3F8 /* DotNetParserFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DotNetParserFormatter.swift; path = Sources/SwiftDate/Formatters/DotNetParserFormatter.swift; sourceTree = ""; }; + 882F435FDF3E0CDABD59D7FEF9958329 /* MJRefresh.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = MJRefresh.bundle; path = MJRefresh/MJRefresh.bundle; sourceTree = ""; }; + 882F5DAB4B06E95F1A5C4237CE16D896 /* MJRefreshComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshComponent.h; path = MJRefresh/Base/MJRefreshComponent.h; sourceTree = ""; }; + 883227B7FBBBCE19FA3C9DABB6257BF5 /* ResponseSerialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResponseSerialization.swift; path = Source/Features/ResponseSerialization.swift; sourceTree = ""; }; + 884C2A1738B60FA764F7F9E770737AF2 /* SwiftDate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftDate.swift; path = Sources/SwiftDate/SwiftDate.swift; sourceTree = ""; }; + 886F4DF873F4332C1BE04E6A33C70192 /* UICollectionViewLayout+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UICollectionViewLayout+MJRefresh.m"; path = "MJRefresh/UICollectionViewLayout+MJRefresh.m"; sourceTree = ""; }; 8927A418B90BECD52B8D147258EB4781 /* Pods-MusicPlayer-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-MusicPlayer-acknowledgements.plist"; sourceTree = ""; }; - 89FA1854F15DFB5CA5EAC2BF88823143 /* JXPagingListRefreshView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingListRefreshView.swift; path = Sources/JXPagingView/JXPagingListRefreshView.swift; sourceTree = ""; }; - 89FE60E3816FD1A788423E90F6E28D8D /* JXSegmentedIndicatorProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorProtocol.swift; path = Sources/Indicator/JXSegmentedIndicatorProtocol.swift; sourceTree = ""; }; - 8A296F41C13699333333D3C630E92F80 /* ImageView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ImageView+Kingfisher.swift"; path = "Sources/Extensions/ImageView+Kingfisher.swift"; sourceTree = ""; }; - 8A2A128F3FAE37601751167FDC47182A /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; - 8A9A6B3FF123B6733156B839AB90A488 /* Session.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Session.swift; path = Source/Core/Session.swift; sourceTree = ""; }; - 8B4EE5D13ED112C5C84DB3A6ADE8E7E0 /* UploadRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UploadRequest.swift; path = Source/Core/UploadRequest.swift; sourceTree = ""; }; - 8CFAC3CD688A5BEB743550BA40C464FF /* JXSegmentedDotItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedDotItemModel.swift; path = Sources/Dot/JXSegmentedDotItemModel.swift; sourceTree = ""; }; - 8E6EB2C04F247D9F4D15B77F830EDF54 /* Date+Compare.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Compare.swift"; path = "Sources/SwiftDate/Date/Date+Compare.swift"; sourceTree = ""; }; - 8F1162C00BCBB4CECFD5CA05ED5667F3 /* MJRefreshNormalHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshNormalHeader.m; path = MJRefresh/Custom/Header/MJRefreshNormalHeader.m; sourceTree = ""; }; + 89A5C6D1F8C391EC0BA7A85FA520C894 /* Array+Safe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Array+Safe.swift"; path = "Sources/Extensions/Array+Safe.swift"; sourceTree = ""; }; + 8A7AE61E79272DD6D46024C339DD62FE /* SVProgressHUD.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVProgressHUD.m; path = SVProgressHUD/SVProgressHUD.m; sourceTree = ""; }; + 8AD66F835AAF00EF7D103A4AA5C1F6E4 /* SessionManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionManager.swift; path = Sources/General/SessionManager.swift; sourceTree = ""; }; + 8D50102F684B52D0EC7CEDB9B357C54E /* URLRequest+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLRequest+Alamofire.swift"; path = "Source/Extensions/URLRequest+Alamofire.swift"; sourceTree = ""; }; + 8DAFB222F308B491B658DA188BE99D5E /* Reachability.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Reachability.debug.xcconfig; sourceTree = ""; }; + 8DBA144AD6F6B2C8309C265DA2BDC2E4 /* Alamofire-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Alamofire-dummy.m"; sourceTree = ""; }; + 8ED521E4CBFEA0F5D9E01C3CCE5307D5 /* JXSegmentedTitleOrImageItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleOrImageItemModel.swift; path = Sources/TitleOrImage/JXSegmentedTitleOrImageItemModel.swift; sourceTree = ""; }; + 8FDAFF7CF1DBCBEBF9C2AA409E378629 /* ResourceBundle-Alamofire-Alamofire-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Alamofire-Alamofire-Info.plist"; sourceTree = ""; }; + 8FE48C9953074AD1749565386F58D202 /* ConstraintMakerRelatable+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintMakerRelatable+Extensions.swift"; path = "Sources/ConstraintMakerRelatable+Extensions.swift"; sourceTree = ""; }; + 90391DB72A0C3217ED3F84491E44D38F /* FSCheckContentTypeRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSCheckContentTypeRequest.h; path = FreeStreamer/FreeStreamer/FSCheckContentTypeRequest.h; sourceTree = ""; }; + 91375183D8F9D59BCF0B45DD272B7E1D /* audio_stream.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = audio_stream.cpp; path = FreeStreamer/FreeStreamer/audio_stream.cpp; sourceTree = ""; }; + 916A56D8864031F453F9C00A745F32D0 /* TiercelError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TiercelError.swift; path = Sources/General/TiercelError.swift; sourceTree = ""; }; + 918BA156001C627978B12B0E996C4789 /* Commons.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Commons.swift; path = Sources/SwiftDate/Supports/Commons.swift; sourceTree = ""; }; + 9218CCDDFFCB0E70020117C647E18758 /* MJRefreshStateHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshStateHeader.h; path = MJRefresh/Custom/Header/MJRefreshStateHeader.h; sourceTree = ""; }; + 921FCA711D3B8BFABCEB7BFD07F64831 /* JXSegmentedViewTool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedViewTool.swift; path = Sources/Common/JXSegmentedViewTool.swift; sourceTree = ""; }; + 923221B8AC80D87BFC467045B41C9B5C /* ISOParser.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISOParser.swift; path = Sources/SwiftDate/Formatters/ISOParser.swift; sourceTree = ""; }; 92B0EC788EDA1B0CFA48DFFCB3DDAECD /* JXSegmentedView-JXSegmentedView */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "JXSegmentedView-JXSegmentedView"; path = JXSegmentedView.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - 94F9116CE6A49B5AFFA19B16D5346243 /* MJRefreshAutoFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoFooter.h; path = MJRefresh/Base/MJRefreshAutoFooter.h; sourceTree = ""; }; - 951E048B27AEAC006B779E585701262E /* DateInRegion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateInRegion.swift; path = Sources/SwiftDate/DateInRegion/DateInRegion.swift; sourceTree = ""; }; - 96222B976672EC3736CB9B53FC49C92E /* UIScrollView+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+MJRefresh.h"; path = "MJRefresh/UIScrollView+MJRefresh.h"; sourceTree = ""; }; + 93A9C6C311E69800A3DBB32A5CE9A829 /* RequestTaskMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestTaskMap.swift; path = Source/Core/RequestTaskMap.swift; sourceTree = ""; }; + 94B85D70EFBE6C79981BAA409C3C02D5 /* AlamofireExtended.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AlamofireExtended.swift; path = Source/Features/AlamofireExtended.swift; sourceTree = ""; }; + 951109FA965A9BDC4FC4E338EE37DE65 /* AFError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AFError.swift; path = Source/Core/AFError.swift; sourceTree = ""; }; + 9598A6AA81E42757DB35F6F39AF08B4D /* file_stream.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = file_stream.cpp; path = FreeStreamer/FreeStreamer/file_stream.cpp; sourceTree = ""; }; + 95F712398C1D7BD6424897C31E306761 /* JXPagingView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "JXPagingView-Info.plist"; sourceTree = ""; }; + 963052BB2E62093DA5CBB64F34A6F258 /* String+Hash.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+Hash.swift"; path = "Sources/Extensions/String+Hash.swift"; sourceTree = ""; }; + 967F95BF5036324BE9254F76C8F11590 /* JXPagingView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingView.swift; path = Sources/JXPagingView/JXPagingView.swift; sourceTree = ""; }; + 968C21562868FE498570DD48476092E9 /* StringEncoding+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "StringEncoding+Alamofire.swift"; path = "Source/Extensions/StringEncoding+Alamofire.swift"; sourceTree = ""; }; + 96B33F56346B35C8103DED7E12D113E9 /* MultipartUpload.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartUpload.swift; path = Source/Features/MultipartUpload.swift; sourceTree = ""; }; + 96F5AE52581DB823B2E91EEB6FD22AF1 /* DownloadTask.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DownloadTask.swift; path = Sources/General/DownloadTask.swift; sourceTree = ""; }; + 975850A5F9F51047F413323BDD9F682E /* JXSegmentedRTLLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedRTLLayout.swift; path = Sources/Common/JXSegmentedRTLLayout.swift; sourceTree = ""; }; 979486118B3E90C08386079D57962701 /* SnapKit */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SnapKit; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 988FB962B33EFE0451CB5B6094DEAA75 /* IQKeyboardManager+Position.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Position.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Position.swift"; sourceTree = ""; }; - 98D0F4FF0A3E2660BE566528FDF31D03 /* IQKeyboardManagerSwift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "IQKeyboardManagerSwift-umbrella.h"; sourceTree = ""; }; - 9A89AE3F00FA615DB614031B7510C78F /* IQKeyboardManagerConstants.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManagerConstants.swift; path = IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstants.swift; sourceTree = ""; }; - 9ABF6319E31E3E401B63195B3374007D /* ConstraintMakerRelatable+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintMakerRelatable+Extensions.swift"; path = "Sources/ConstraintMakerRelatable+Extensions.swift"; sourceTree = ""; }; - 9AC28BE75055A99E1BD5B4B8D07E3C1B /* ParameterEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoder.swift; path = Source/Core/ParameterEncoder.swift; sourceTree = ""; }; - 9B189CE2110C0A21199EB8FB87FCEB6D /* SVProgressHUD.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVProgressHUD.m; path = SVProgressHUD/SVProgressHUD.m; sourceTree = ""; }; - 9B33B085086724D9914992FFDEF83E0B /* DateInRegion+Compare.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Compare.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Compare.swift"; sourceTree = ""; }; - 9CC5E0E6473324DB3F517B54470CBE6C /* ConstraintViewDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintViewDSL.swift; path = Sources/ConstraintViewDSL.swift; sourceTree = ""; }; + 979BC6537C00870AA3E990B2726CA8B8 /* MJRefresh-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "MJRefresh-dummy.m"; sourceTree = ""; }; + 97FF07A23DCF1ECAAE9FF2E505D9B5C9 /* MJRefreshTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshTrailer.m; path = MJRefresh/Base/MJRefreshTrailer.m; sourceTree = ""; }; + 9805317D96D305A7B55B34F9ED5D7FC5 /* DateInRegion+Create.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Create.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Create.swift"; sourceTree = ""; }; + 98621F211CCD821D8BED35AE739E356F /* MJRefreshStateTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshStateTrailer.h; path = MJRefresh/Custom/Trailer/MJRefreshStateTrailer.h; sourceTree = ""; }; + 98AA35E31BC370E094419B4110DEBB61 /* Resource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Resource.swift; path = Sources/General/ImageSource/Resource.swift; sourceTree = ""; }; + 98B283FDE7FACB318CC1398FECAB76E7 /* ImageCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = Sources/Cache/ImageCache.swift; sourceTree = ""; }; + 98D2AD7AD0CA81C7FE5D1D243FE4573E /* Debugging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Debugging.swift; path = Sources/Debugging.swift; sourceTree = ""; }; + 98F57077D04460183803BC6B5E9765D7 /* input_stream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = input_stream.h; path = FreeStreamer/FreeStreamer/input_stream.h; sourceTree = ""; }; + 9907E83DA970901BFCAAF3A5FDCAC9ED /* KingfisherManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherManager.swift; path = Sources/General/KingfisherManager.swift; sourceTree = ""; }; + 9A8CE17F7BEFB6AF765222B453E745F0 /* IQNSArray+Sort.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQNSArray+Sort.swift"; path = "IQKeyboardManagerSwift/Categories/IQNSArray+Sort.swift"; sourceTree = ""; }; + 9ABA7A1E43832CC4FDC1B0589A531B91 /* Image.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Image.swift; path = Sources/Image/Image.swift; sourceTree = ""; }; + 9B18DFF40BBC24DB3E0E6AFD51871F38 /* ConstraintPriority.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintPriority.swift; path = Sources/ConstraintPriority.swift; sourceTree = ""; }; + 9B4B0F4ACB790F3A81542BAEB92997B8 /* Kingfisher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Kingfisher.modulemap; sourceTree = ""; }; + 9BE8B8F2FF82C76A9EF65784FB0C5A6E /* MJRefreshBackStateFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackStateFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m; sourceTree = ""; }; + 9C54ECE18F0497523A08897E77463C71 /* Concurrency.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Concurrency.swift; path = Source/Features/Concurrency.swift; sourceTree = ""; }; + 9C6287F6DCA0BCB29F738E6D22B4F9F6 /* URLSession+ResumeData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLSession+ResumeData.swift"; path = "Sources/Extensions/URLSession+ResumeData.swift"; sourceTree = ""; }; + 9C865CCD1F85473AC6AEF3AF846C60A5 /* SwiftDate-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftDate-umbrella.h"; sourceTree = ""; }; + 9CD589D0CF5798FFB6E46700B50175E2 /* ImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProvider.swift; path = Sources/General/ImageSource/ImageDataProvider.swift; sourceTree = ""; }; + 9CDB522A56DDB4AA1E1324E6A24C7710 /* ConstraintDirectionalInsets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDirectionalInsets.swift; path = Sources/ConstraintDirectionalInsets.swift; sourceTree = ""; }; + 9D2626745F360CF15E27E55F46760135 /* SVProgressHUD.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = SVProgressHUD.bundle; path = SVProgressHUD/SVProgressHUD.bundle; sourceTree = ""; }; + 9D6E75DD4281F6901C196B196AC70734 /* TVMonogramView+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "TVMonogramView+Kingfisher.swift"; path = "Sources/Extensions/TVMonogramView+Kingfisher.swift"; sourceTree = ""; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9DB02FC7AC88AE54321E64548CBFD1FF /* SnapKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SnapKit.release.xcconfig; sourceTree = ""; }; - 9E1F7BD2E4AFF6989C1387F2558185ED /* SessionDelegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionDelegate.swift; path = Sources/Networking/SessionDelegate.swift; sourceTree = ""; }; - A0422DB62B6B3C1A201A2762EBBB5A97 /* Date.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Date.swift; path = Sources/SwiftDate/Date/Date.swift; sourceTree = ""; }; - A04C1CC2D7176FD20CE057E555EFC2CB /* MJRefreshAutoNormalFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoNormalFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.m; sourceTree = ""; }; - A10B7F476AA2BC85DA32CDCA695E94CF /* MJRefreshAutoGifFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoGifFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.m; sourceTree = ""; }; - A206F578A0BA9FC1C6B8B6078EFF6B9A /* SVProgressHUD.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = SVProgressHUD.bundle; path = SVProgressHUD/SVProgressHUD.bundle; sourceTree = ""; }; - A26DF092CAB68C5202C9271E71C8F750 /* Constraint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Constraint.swift; path = Sources/Constraint.swift; sourceTree = ""; }; - A3841A0A7F5A5EABE2100147A429E0C6 /* IQKeyboardManagerSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IQKeyboardManagerSwift.debug.xcconfig; sourceTree = ""; }; - A418AA4ADA9598EFD690FF4427F182A5 /* UICollectionViewLayout+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UICollectionViewLayout+MJRefresh.h"; path = "MJRefresh/UICollectionViewLayout+MJRefresh.h"; sourceTree = ""; }; + 9E1879F0CCEF6A60749ADDF0C4C21F45 /* input_stream.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = input_stream.cpp; path = FreeStreamer/FreeStreamer/input_stream.cpp; sourceTree = ""; }; + 9F1D98A6ED81F7F2FFE1483C15157751 /* IQUIView+IQKeyboardToolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIView+IQKeyboardToolbar.swift"; path = "IQKeyboardManagerSwift/IQToolbar/IQUIView+IQKeyboardToolbar.swift"; sourceTree = ""; }; + 9F7BC3B5F563719E7C085370A8A3E353 /* JXSegmentedView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXSegmentedView-prefix.pch"; sourceTree = ""; }; + A1EF6CE4DCA3A7EB57EF30D95912E1CC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; + A315721095BED3E19F0DA90AB3FB056A /* Data+Hash.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Data+Hash.swift"; path = "Sources/Extensions/Data+Hash.swift"; sourceTree = ""; }; + A3E2B8B92BC8BB5E3EEF4A78D2464797 /* JXSegmentedTitleImageDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleImageDataSource.swift; path = Sources/TitleImage/JXSegmentedTitleImageDataSource.swift; sourceTree = ""; }; A46268EC24FA52F2CB5649EC2E7A996C /* Pods-MusicPlayer-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-MusicPlayer-acknowledgements.markdown"; sourceTree = ""; }; - A47365FDDB9CB0AC423AD6C3AF49AD21 /* KingfisherOptionsInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherOptionsInfo.swift; path = Sources/General/KingfisherOptionsInfo.swift; sourceTree = ""; }; - A56C8749F94C4894BDE28709B22BC8EC /* MJRefreshTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshTrailer.h; path = MJRefresh/Base/MJRefreshTrailer.h; sourceTree = ""; }; - A690FBA7765087019C4F3AFEDD3E2CB0 /* ResourceBundle-JXPagingView-JXPagingView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-JXPagingView-JXPagingView-Info.plist"; sourceTree = ""; }; - A78C6D5D45F26A283595741E6566040D /* ConstraintPriority.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintPriority.swift; path = Sources/ConstraintPriority.swift; sourceTree = ""; }; - A793885CE536998701B9632A60B45781 /* WebSocketRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = WebSocketRequest.swift; path = Source/Core/WebSocketRequest.swift; sourceTree = ""; }; - A7AF9A69BFF4975345B92D217643792D /* Kingfisher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Kingfisher.modulemap; sourceTree = ""; }; + A5097DCB7A30413A98441BDB44CF3B14 /* IQKeyboardManager+OrientationNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+OrientationNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+OrientationNotification.swift"; sourceTree = ""; }; + A5B2767297008EECD4B485DFC38F2111 /* DispatchQueue+Safe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DispatchQueue+Safe.swift"; path = "Sources/Extensions/DispatchQueue+Safe.swift"; sourceTree = ""; }; + A5CBEE13981AE8C02C5198ADE53051B6 /* JXSegmentedBaseDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedBaseDataSource.swift; path = Sources/Core/JXSegmentedBaseDataSource.swift; sourceTree = ""; }; + A794BB26E2D7D9E1AEEB5DB848440C14 /* Reachability-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Reachability-umbrella.h"; sourceTree = ""; }; + A7CEF4C6C4AFAC58F97E11BC9EA0484D /* SnapKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SnapKit-Info.plist"; sourceTree = ""; }; A8E950A16D00F649C54FFB30F81D7842 /* IQKeyboardManagerSwift */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = IQKeyboardManagerSwift; path = IQKeyboardManagerSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - A933F95E4E12D348E925A9E3B27750CB /* SnapKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SnapKit-dummy.m"; sourceTree = ""; }; - AA2205F6575D1182446627195A2B2D7D /* SwiftDate.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftDate.release.xcconfig; sourceTree = ""; }; - AA35F74EEBAD4E0CB28C015AD5D6EECF /* Alamofire-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Alamofire-dummy.m"; sourceTree = ""; }; - AA39C6D2448DC5A9AD18DDA3C96A1A0F /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; }; - AAA467CC8E830A4407AEEA9E195B5572 /* IQKeyboardManager+UIKeyboardNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+UIKeyboardNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+UIKeyboardNotification.swift"; sourceTree = ""; }; - AB0205A7B7089CE3AA7AF45ACDE3629A /* JXSegmentedRTLLayout.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedRTLLayout.swift; path = Sources/Common/JXSegmentedRTLLayout.swift; sourceTree = ""; }; - AB089815F261E66CDE44D5F4DE72EE32 /* IQKeyboardManagerSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = IQKeyboardManagerSwift.release.xcconfig; sourceTree = ""; }; - AC5E8113942F4B6D5C87580AA549D92C /* MJRefresh.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.release.xcconfig; sourceTree = ""; }; - ADD37EAEC18687AE8ED7C2EED0B1463A /* MJRefresh-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MJRefresh-prefix.pch"; sourceTree = ""; }; - AE5B1F68CF04B5EBB405EA5A612234B6 /* SVRadialGradientLayer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVRadialGradientLayer.m; path = SVProgressHUD/SVRadialGradientLayer.m; sourceTree = ""; }; - AE8FD0EA90E35589FF45377FFE6445D6 /* IQKeyboardManagerSwift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = IQKeyboardManagerSwift.modulemap; sourceTree = ""; }; - AFCD84A7AB4756552C1E55E266165ACA /* JXSegmentedTitleOrImageDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleOrImageDataSource.swift; path = Sources/TitleOrImage/JXSegmentedTitleOrImageDataSource.swift; sourceTree = ""; }; - AFFBC6D1E5E46588B6AAB4F0711B9D5E /* JXSegmentedNumberCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedNumberCell.swift; path = Sources/Number/JXSegmentedNumberCell.swift; sourceTree = ""; }; - B06108CF75D8C3024C894159C039AF1C /* SVProgressHUD.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SVProgressHUD.debug.xcconfig; sourceTree = ""; }; - B0CE74EB3DE207E3CAE98CDE17280DBA /* JXSegmentedIndicatorDotLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorDotLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorDotLineView.swift; sourceTree = ""; }; - B10D7C8D9A8EFE21347CF640AE5AFCD9 /* RetryPolicy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RetryPolicy.swift; path = Source/Features/RetryPolicy.swift; sourceTree = ""; }; - B2E886364CE3D1DE31930B53C67EFBEB /* IQKeyboardManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManager.swift; path = IQKeyboardManagerSwift/IQKeyboardManager.swift; sourceTree = ""; }; - B4111D05E2FD45B47E3E6AD830625F74 /* NSBundle+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSBundle+MJRefresh.h"; path = "MJRefresh/NSBundle+MJRefresh.h"; sourceTree = ""; }; - B43CFD3981B70FD7D609A5839B6A9218 /* KFImageRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImageRenderer.swift; path = Sources/SwiftUI/KFImageRenderer.swift; sourceTree = ""; }; - B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - B5819E5E132D7A725A5CF03163E7796D /* langs */ = {isa = PBXFileReference; includeInIndex = 1; name = langs; path = Sources/SwiftDate/Formatters/RelativeFormatter/langs; sourceTree = ""; }; - B65D6A9EE341D83823E03C122A72A5C1 /* JXSegmentedTitleImageItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleImageItemModel.swift; path = Sources/TitleImage/JXSegmentedTitleImageItemModel.swift; sourceTree = ""; }; - B66FA26ED5139D4710E18EA56AEC64BE /* Kingfisher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Kingfisher-prefix.pch"; sourceTree = ""; }; - B764532F7A465FF640FDA97A850A103D /* MJRefreshStateTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshStateTrailer.h; path = MJRefresh/Custom/Trailer/MJRefreshStateTrailer.h; sourceTree = ""; }; - B860B422FB58A9842214FE11A933BBD4 /* IQBarButtonItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQBarButtonItem.swift; path = IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift; sourceTree = ""; }; - B86EE2D4250A56EE1BB39FFEB834371D /* ConstraintMakerRelatable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerRelatable.swift; path = Sources/ConstraintMakerRelatable.swift; sourceTree = ""; }; - B98A705AFD36170CD85E492121E37403 /* AFError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AFError.swift; path = Source/Core/AFError.swift; sourceTree = ""; }; + A9E038FB2AF6F7D78755CD706F903925 /* ConstraintPriorityTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintPriorityTarget.swift; path = Sources/ConstraintPriorityTarget.swift; sourceTree = ""; }; + AA836459635F8DAA87C15B9C0EC55B49 /* JXSegmentedIndicatorBaseView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorBaseView.swift; path = Sources/Indicator/JXSegmentedIndicatorBaseView.swift; sourceTree = ""; }; + AB2F1CC639A1BFB895317FCBAF22C6CB /* IQKeyboardManager+UITextFieldViewNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+UITextFieldViewNotification.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+UITextFieldViewNotification.swift"; sourceTree = ""; }; + AB406B5AB28CD8F5747EBE9498A2F869 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + AD260DDFBA4FDA84F5C6A25B2CB47470 /* IQKeyboardManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManager.swift; path = IQKeyboardManagerSwift/IQKeyboardManager.swift; sourceTree = ""; }; + AD2627618EECADAAD4DB704429147B41 /* MJRefreshHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshHeader.m; path = MJRefresh/Base/MJRefreshHeader.m; sourceTree = ""; }; + ADE2F4AB9CA5430A26D688A9559F54B5 /* ResourceBundle-JXPagingView-JXPagingView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-JXPagingView-JXPagingView-Info.plist"; sourceTree = ""; }; + ADE694B2F5E068593CAAF87E546F52F1 /* caching_stream.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = caching_stream.cpp; path = FreeStreamer/FreeStreamer/caching_stream.cpp; sourceTree = ""; }; + AE492A9624C33092DFCBC183ECADE79D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + AE514086343B4C0D395517B412878058 /* Zones.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Zones.swift; path = Sources/SwiftDate/Supports/Zones.swift; sourceTree = ""; }; + AE8226608B94B7947A425E8D90B33C1E /* IQBarButtonItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQBarButtonItem.swift; path = IQKeyboardManagerSwift/IQToolbar/IQBarButtonItem.swift; sourceTree = ""; }; + AEAB2E073749D67141BC95939FD1DAF9 /* KFImageProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImageProtocol.swift; path = Sources/SwiftUI/KFImageProtocol.swift; sourceTree = ""; }; + AECF3DE66EAD86C69CFB89D6FDEE4268 /* FreeStreamer-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FreeStreamer-umbrella.h"; sourceTree = ""; }; + AF9FD0EA190BF89FE270D6BAE52B3067 /* MJRefreshTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshTrailer.h; path = MJRefresh/Base/MJRefreshTrailer.h; sourceTree = ""; }; + AFDB43B2AA4D143F40F21098E3079899 /* SwiftDate.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftDate.debug.xcconfig; sourceTree = ""; }; + B022AD44B90697AF0A3D63247178D60D /* Tiercel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Tiercel.release.xcconfig; sourceTree = ""; }; + B06F8788675D48A6E7D3FAEDFB453AAA /* Date+Create.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Create.swift"; path = "Sources/SwiftDate/Date/Date+Create.swift"; sourceTree = ""; }; + B11D4A2DB281C9D6FC1C296D5E1AA3F9 /* ConstraintInsets.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintInsets.swift; path = Sources/ConstraintInsets.swift; sourceTree = ""; }; + B12082E67BEB2172AFEE6485633D64DF /* NSBundle+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSBundle+MJRefresh.m"; path = "MJRefresh/NSBundle+MJRefresh.m"; sourceTree = ""; }; + B26FEF928162FDEE5CFCE3EF99770572 /* ConstraintLayoutSupportDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintLayoutSupportDSL.swift; path = Sources/ConstraintLayoutSupportDSL.swift; sourceTree = ""; }; + B31511FD256433B92C0FEEE45809A116 /* ImagePrefetcher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImagePrefetcher.swift; path = Sources/Networking/ImagePrefetcher.swift; sourceTree = ""; }; + B4E3529EFC2A1018DD6CBC38DC725AFF /* file_stream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = file_stream.h; path = FreeStreamer/FreeStreamer/file_stream.h; sourceTree = ""; }; + B504D736329CAD81C67C2A8FC1F582A7 /* JXSegmentedTitleGradientCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleGradientCell.swift; path = Sources/TitleGradient/JXSegmentedTitleGradientCell.swift; sourceTree = ""; }; + B57AAC6360170C5570DBED4913F8439E /* ConstraintView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintView.swift; path = Sources/ConstraintView.swift; sourceTree = ""; }; + B66B6DFD66EDD8490CD94EC55E8E7B64 /* CPListItem+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "CPListItem+Kingfisher.swift"; path = "Sources/Extensions/CPListItem+Kingfisher.swift"; sourceTree = ""; }; + B6E3C1F981C5074CEFE799E99CBAC5B3 /* JXSegmentedTitleItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleItemModel.swift; path = Sources/Title/JXSegmentedTitleItemModel.swift; sourceTree = ""; }; + B90B184E14693E477A94423C02031AB5 /* ConstraintDSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDSL.swift; path = Sources/ConstraintDSL.swift; sourceTree = ""; }; + B95736ACF688CE97879CE512184557E1 /* MJRefreshConfig.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshConfig.h; path = MJRefresh/MJRefreshConfig.h; sourceTree = ""; }; + B9C3E77992D38B50A812470C4F23CA7B /* Alamofire-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Alamofire-Info.plist"; sourceTree = ""; }; B9DCB5EC0B1CDADD221717CADDF62359 /* SnapKit-SnapKit_Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "SnapKit-SnapKit_Privacy"; path = SnapKit_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - BA90CEFF373663026D2F06371A3D4E9A /* JXSegmentedListContainerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedListContainerView.swift; path = Sources/Common/JXSegmentedListContainerView.swift; sourceTree = ""; }; - BAAC3162B7531D025AB197602FE75F31 /* ServerTrustEvaluation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServerTrustEvaluation.swift; path = Source/Features/ServerTrustEvaluation.swift; sourceTree = ""; }; - BB17A1319D51982E2F7A99EFC3D11F38 /* MJRefreshConfig.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshConfig.h; path = MJRefresh/MJRefreshConfig.h; sourceTree = ""; }; - BB420F181A7222985A450867020EFB2C /* JXPagingMainTableView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingMainTableView.swift; path = Sources/JXPagingView/JXPagingMainTableView.swift; sourceTree = ""; }; - BB993F1FCBAA91D44D43B0587DB633FB /* SnapKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SnapKit-Info.plist"; sourceTree = ""; }; - BC90E0F634A75E57580CB83CB0B039E4 /* SnapKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SnapKit.debug.xcconfig; sourceTree = ""; }; - BD1BE557E2AE4A84F2515EB14ABAF97B /* SVProgressAnimatedView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVProgressAnimatedView.m; path = SVProgressHUD/SVProgressAnimatedView.m; sourceTree = ""; }; - BD56DAD13E1185FD74DB7A8649E746CE /* Int+DateComponents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Int+DateComponents.swift"; path = "Sources/SwiftDate/Foundation+Extras/Int+DateComponents.swift"; sourceTree = ""; }; - BDCF0C8D768EFB2E7C6A48EB2256867E /* CPListItem+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "CPListItem+Kingfisher.swift"; path = "Sources/Extensions/CPListItem+Kingfisher.swift"; sourceTree = ""; }; - BE0D9928CC4ABC217232D13CC2A41C53 /* SwiftDate.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftDate.debug.xcconfig; sourceTree = ""; }; - BE633CEBF91C6D351A438EB13D3F4E1F /* KF.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KF.swift; path = Sources/General/KF.swift; sourceTree = ""; }; - BE8BB5A012C6C501F12A4876ABF49101 /* MJRefreshAutoStateFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoStateFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m; sourceTree = ""; }; - BF6B66846BA638DCD6F5ABB338C4AA41 /* Notifications.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Notifications.swift; path = Source/Core/Notifications.swift; sourceTree = ""; }; - C07BEF0CE5DECC5BDCAC8625BF2FFA4B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; + BAC58F8D0F7FE424A8AD14D75A30EED3 /* SVIndefiniteAnimatedView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVIndefiniteAnimatedView.h; path = SVProgressHUD/SVIndefiniteAnimatedView.h; sourceTree = ""; }; + BAD7B276B5B5105C81921E0B4E88DF8C /* SnapKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SnapKit.modulemap; sourceTree = ""; }; + BAF56CAE407A1246D8E33A86EEAB1CD0 /* Notifications.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Notifications.swift; path = Source/Core/Notifications.swift; sourceTree = ""; }; + BC1880EFAED23577D0DE35F1DF62E176 /* ImageFormat.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageFormat.swift; path = Sources/Image/ImageFormat.swift; sourceTree = ""; }; + BDBEAB153F0F1BFE8EE317B7794B999E /* FreeStreamer-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FreeStreamer-dummy.m"; sourceTree = ""; }; + BDE49DC44DD670EFE086D67A7A64F08A /* IQUIView+Hierarchy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIView+Hierarchy.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift"; sourceTree = ""; }; + BE510A9C1FFF14CCF35D6CC112249B7B /* GraphicsContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GraphicsContext.swift; path = Sources/Image/GraphicsContext.swift; sourceTree = ""; }; + BE9B2D284160E9548EBF96125E9B7554 /* ParameterEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ParameterEncoder.swift; path = Source/Core/ParameterEncoder.swift; sourceTree = ""; }; + BEB7265F1F1E1E6447ED57D564548109 /* ConstraintItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintItem.swift; path = Sources/ConstraintItem.swift; sourceTree = ""; }; + BFF72AA8AFE7B31BDC964105FF4B8FF0 /* SVProgressHUD.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVProgressHUD.h; path = SVProgressHUD/SVProgressHUD.h; sourceTree = ""; }; + C00F9A44E0AE14DC8B34030B2DA760B3 /* IQUIViewController+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIViewController+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIViewController+Additions.swift"; sourceTree = ""; }; + C01605AC5DD0430BA05B21A288DA50C1 /* MJRefreshNormalTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshNormalTrailer.h; path = MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.h; sourceTree = ""; }; + C01E50B447FBD15488751F2415EB6952 /* NSBundle+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSBundle+MJRefresh.h"; path = "MJRefresh/NSBundle+MJRefresh.h"; sourceTree = ""; }; + C15D0636F86B144901B1E79D45A5EF72 /* JXSegmentedTitleOrImageCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleOrImageCell.swift; path = Sources/TitleOrImage/JXSegmentedTitleOrImageCell.swift; sourceTree = ""; }; + C170E9CBAFE070E5D1B42846B3B96DDA /* MJRefresh.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = MJRefresh.modulemap; sourceTree = ""; }; + C1881FE3DF05060537FF888C35D42D74 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/MediaPlayer.framework; sourceTree = DEVELOPER_DIR; }; C298ABB78D9B05529B89D8322DB2E7B0 /* Kingfisher-Kingfisher */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "Kingfisher-Kingfisher"; path = Kingfisher.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; - C2DC9D938B1784B3E0E8FFC9851FCAEA /* JXPagingListContainerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingListContainerView.swift; path = Sources/JXPagingView/JXPagingListContainerView.swift; sourceTree = ""; }; - C33A744358DA725C9E769351AB40C521 /* SwiftDate-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SwiftDate-prefix.pch"; sourceTree = ""; }; + C2E016699F6BADA8EE18EC03EEAF1CC8 /* IQInvocation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQInvocation.swift; path = IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift; sourceTree = ""; }; + C39BDFBFDF3DC43026E7C749C9D7E00A /* DateInRegion+Components.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Components.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Components.swift"; sourceTree = ""; }; C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Kingfisher; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C408D0CD682A652982659893260F5EA4 /* ImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProvider.swift; path = Sources/General/ImageSource/ImageDataProvider.swift; sourceTree = ""; }; - C468581B4214846A9CA4FFED32F836D8 /* TimeStructures.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimeStructures.swift; path = Sources/SwiftDate/Supports/TimeStructures.swift; sourceTree = ""; }; - C483D88FC9B266C71FB54AA488C58BBD /* ConstraintRelatableTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintRelatableTarget.swift; path = Sources/ConstraintRelatableTarget.swift; sourceTree = ""; }; - C4A16ABD25AB8AAB13E0072CADE42E65 /* UILayoutSupport+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UILayoutSupport+Extensions.swift"; path = "Sources/UILayoutSupport+Extensions.swift"; sourceTree = ""; }; - C4C51E9357636F513F9828759A831069 /* DateInRegion+Create.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Create.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Create.swift"; sourceTree = ""; }; - C50C61572CB504F3A1B27F44B20F385F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; - C521434F700F146BC7EEBBE2C6735ABF /* JXPagingView.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = JXPagingView.modulemap; sourceTree = ""; }; - C54C3E1777728F805F3EFCB27BB703BC /* IQUIScrollView+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIScrollView+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift"; sourceTree = ""; }; - C68BBA48149EB58E146B969600FB99B8 /* SVProgressHUD-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-umbrella.h"; sourceTree = ""; }; - C76A1ABBD38622E820640903E4A979AD /* MJRefreshFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshFooter.m; path = MJRefresh/Base/MJRefreshFooter.m; sourceTree = ""; }; - C94A8D6D5B3A24A5E000AF4DBE9434CF /* MultipartUpload.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartUpload.swift; path = Source/Features/MultipartUpload.swift; sourceTree = ""; }; - CA08845BC3434B44AF0A3C8AFC111D93 /* JXSegmentedBaseCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedBaseCell.swift; path = Sources/Core/JXSegmentedBaseCell.swift; sourceTree = ""; }; - CA42DDB21EAB3527641F275C3D22E4D0 /* Validation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validation.swift; path = Source/Features/Validation.swift; sourceTree = ""; }; - CA5885E0EE3717A1437053591B37EAD8 /* SwiftDate-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SwiftDate-Info.plist"; sourceTree = ""; }; - CB03A8FE1983F5F495EC9ADC0D6E7E6B /* MultipartFormData.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MultipartFormData.swift; path = Source/Features/MultipartFormData.swift; sourceTree = ""; }; - CB0F440BD5C5987BBAD5992F1CC0CCAA /* Result+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Result+Alamofire.swift"; path = "Source/Extensions/Result+Alamofire.swift"; sourceTree = ""; }; - CB34508FC3184D3482A1002BEBC16654 /* IQKeyboardManager+Toolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Toolbar.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Toolbar.swift"; sourceTree = ""; }; - CDC741E10FE2F276DD3C474545224A00 /* FormatIndicatedCacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FormatIndicatedCacheSerializer.swift; path = Sources/Cache/FormatIndicatedCacheSerializer.swift; sourceTree = ""; }; - CDCBF5F15899BDBF63D3CC736990E1A5 /* IQKeyboardReturnKeyHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardReturnKeyHandler.swift; path = IQKeyboardManagerSwift/IQKeyboardReturnKeyHandler.swift; sourceTree = ""; }; - CE14F9280BCE30360011E5C9806D2516 /* ResourceBundle-Alamofire-Alamofire-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Alamofire-Alamofire-Info.plist"; sourceTree = ""; }; - CF14834E63FED3D59E4631FEEA9514CD /* MJRefresh.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = MJRefresh.modulemap; sourceTree = ""; }; - CFCB35EFBB8A545F2F8352A7D27D5B2A /* ExtensionHelpers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExtensionHelpers.swift; path = Sources/Utility/ExtensionHelpers.swift; sourceTree = ""; }; - D061CC1A5FB61D63B44C75541D898A36 /* ImageContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageContext.swift; path = Sources/SwiftUI/ImageContext.swift; sourceTree = ""; }; - D18ABD1C495B7F72F0284AAC613A93F2 /* MJRefreshNormalTrailer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshNormalTrailer.h; path = MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.h; sourceTree = ""; }; - D1968FDA9F67DC3333951BED4268CA52 /* Calendars.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Calendars.swift; path = Sources/SwiftDate/Supports/Calendars.swift; sourceTree = ""; }; - D19D1617C2FB2FC864606303AF174A6D /* TimePeriodChain.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodChain.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodChain.swift; sourceTree = ""; }; - D38764AB52ACD239BAD933070283AA4F /* UIScrollView+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+MJRefresh.m"; path = "MJRefresh/UIScrollView+MJRefresh.m"; sourceTree = ""; }; - D394AD7BFA59E7271EC623CF17FBDD30 /* GIFAnimatedImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GIFAnimatedImage.swift; path = Sources/Image/GIFAnimatedImage.swift; sourceTree = ""; }; - D44F4C1AC6C2A207BC560F616003B1B3 /* SVProgressHUD-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-prefix.pch"; sourceTree = ""; }; - D473476568C2D2300CCBAE8BAC883DCE /* JXPagingView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXPagingView.release.xcconfig; sourceTree = ""; }; - D49B5FF4D24E46BB7F695E22A9907317 /* Delegate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Delegate.swift; path = Sources/Utility/Delegate.swift; sourceTree = ""; }; - D5A9B7775663040A8BD50618EDF84298 /* MJRefreshBackFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackFooter.h; path = MJRefresh/Base/MJRefreshBackFooter.h; sourceTree = ""; }; - D66CFC994F45F4A9A50C54C16724D763 /* UIButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIButton+Kingfisher.swift"; path = "Sources/Extensions/UIButton+Kingfisher.swift"; sourceTree = ""; }; - D776B0C4D357F0B0466B42C8BF6495D5 /* ImageModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageModifier.swift; path = Sources/Networking/ImageModifier.swift; sourceTree = ""; }; - D88F8FEC13A3A1DB04F7186FA15AE1CB /* ResponseSerialization.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ResponseSerialization.swift; path = Source/Features/ResponseSerialization.swift; sourceTree = ""; }; - D89E20D36B74F4A890688A090C311698 /* RequestInterceptor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestInterceptor.swift; path = Source/Features/RequestInterceptor.swift; sourceTree = ""; }; - D96D6359D0BE44C30CD6EE91D83FA37B /* SVProgressHUD-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SVProgressHUD-Info.plist"; sourceTree = ""; }; - DA1E44B32910D0CD9054692D4F938398 /* ImageDownloader.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDownloader.swift; path = Sources/Networking/ImageDownloader.swift; sourceTree = ""; }; - DBF28FA874E18F2693E3B035356382F0 /* JXSegmentedTitleItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleItemModel.swift; path = Sources/Title/JXSegmentedTitleItemModel.swift; sourceTree = ""; }; - DC81E237B242F0601E0FC35F98708DC4 /* IQUIView+IQKeyboardToolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIView+IQKeyboardToolbar.swift"; path = "IQKeyboardManagerSwift/IQToolbar/IQUIView+IQKeyboardToolbar.swift"; sourceTree = ""; }; - DED1174C630E86B387EC34B8B1DD2DA3 /* Date+Math.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Math.swift"; path = "Sources/SwiftDate/Date/Date+Math.swift"; sourceTree = ""; }; - DF2C075BA691343B5759F4F639FA08A6 /* Kingfisher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.debug.xcconfig; sourceTree = ""; }; - E0E418BB3B470DAB2533C31BAD7884C0 /* MJRefreshStateTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshStateTrailer.m; path = MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m; sourceTree = ""; }; - E1176C58859CF5334183B7786DAC8E87 /* IQKeyboardManagerSwift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "IQKeyboardManagerSwift-dummy.m"; sourceTree = ""; }; - E17C586A0C27A8F810C609FC78419447 /* IQKeyboardManager+Debug.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Debug.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Debug.swift"; sourceTree = ""; }; - E18618FEAC0ECEE393834FA4EEDDD337 /* Kingfisher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.release.xcconfig; sourceTree = ""; }; - E31B1C576F8187B6B1A1F160B90D6081 /* JXSegmentedIndicatorBackgroundView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorBackgroundView.swift; path = Sources/Indicator/JXSegmentedIndicatorBackgroundView.swift; sourceTree = ""; }; - E3C11DA33AB8B0E9203520516067EC23 /* UIView+MJExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+MJExtension.m"; path = "MJRefresh/UIView+MJExtension.m"; sourceTree = ""; }; - E4678D7C77B6546A4205C194E9F275EB /* MJRefreshNormalHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshNormalHeader.h; path = MJRefresh/Custom/Header/MJRefreshNormalHeader.h; sourceTree = ""; }; - E48EF69AA55D5B91B47D8C1EB4BA4691 /* DateInRegion+Components.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateInRegion+Components.swift"; path = "Sources/SwiftDate/DateInRegion/DateInRegion+Components.swift"; sourceTree = ""; }; + C48D633A21657F503CDC4FC9CE8A949E /* SnapKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SnapKit-dummy.m"; sourceTree = ""; }; + C5BC05F5D88A6640656E78CCE8D5018D /* FSParseRssPodcastFeedRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSParseRssPodcastFeedRequest.m; path = FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.m; sourceTree = ""; }; + C6909F184654EB793056705412217353 /* Kingfisher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.release.xcconfig; sourceTree = ""; }; + C6B4928DAC11CD0F28D07D85993E6E3B /* JXSegmentedComponetGradientView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedComponetGradientView.swift; path = Sources/Indicator/JXSegmentedComponetGradientView.swift; sourceTree = ""; }; + C72132A3576811404B6A267878A318E7 /* Kingfisher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Kingfisher-Info.plist"; sourceTree = ""; }; + C80011A076CE73595572EF851C7E6A04 /* ServerTrustEvaluation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServerTrustEvaluation.swift; path = Source/Features/ServerTrustEvaluation.swift; sourceTree = ""; }; + C878F42BB922E2D8ABB27AB7491FFE39 /* JXSegmentedIndicatorDoubleLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorDoubleLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorDoubleLineView.swift; sourceTree = ""; }; + C87A37B7FF63CC268718D512871DDD82 /* ConstraintOffsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintOffsetTarget.swift; path = Sources/ConstraintOffsetTarget.swift; sourceTree = ""; }; + C96629463BEDB7A830E9D20B58BF2F36 /* JXPagingView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXPagingView.release.xcconfig; sourceTree = ""; }; + C9929DB3FD7ABBE4F7AD3D7BA8F4ED4D /* JXSegmentedTitleGradientDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleGradientDataSource.swift; path = Sources/TitleGradient/JXSegmentedTitleGradientDataSource.swift; sourceTree = ""; }; + C9A720F6975F8168D2D97AFD10E7D7C3 /* JXSegmentedDotDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedDotDataSource.swift; path = Sources/Dot/JXSegmentedDotDataSource.swift; sourceTree = ""; }; + C9F3AF20D10C3D67C0A9CBA28E59360D /* MJRefreshAutoNormalFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoNormalFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.m; sourceTree = ""; }; + C9F8DEB6C11CB3F37C7618260A212B87 /* CacheSerializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CacheSerializer.swift; path = Sources/Cache/CacheSerializer.swift; sourceTree = ""; }; + CA0AA2C2CEE56C08EFDF78A739C0067B /* FSAudioStream.mm */ = {isa = PBXFileReference; includeInIndex = 1; name = FSAudioStream.mm; path = FreeStreamer/FreeStreamer/FSAudioStream.mm; sourceTree = ""; }; + CA4DBCFFFB504E9F7837D6E205CD534A /* ConstraintMakerFinalizable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerFinalizable.swift; path = Sources/ConstraintMakerFinalizable.swift; sourceTree = ""; }; + CA8217C50A39041C540DA99EAAE34AFE /* Kingfisher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Kingfisher.debug.xcconfig; sourceTree = ""; }; + CAA09510E6E35CC1A196B0830CF9D6F9 /* Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Kingfisher.swift; path = Sources/General/Kingfisher.swift; sourceTree = ""; }; + CAAE3ADACEB4C422DD913DE51816FA68 /* ConstraintRelation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintRelation.swift; path = Sources/ConstraintRelation.swift; sourceTree = ""; }; + CB1399E47A9873040FC5D3A42F382917 /* JXPagingSmoothView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingSmoothView.swift; path = Sources/JXPagingView/JXPagingSmoothView.swift; sourceTree = ""; }; + CB2EF2CB55AE8534BFE107E6DC7FFC4D /* Protected.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Protected.swift; path = Sources/General/Protected.swift; sourceTree = ""; }; + CB76AA0035D5AC744AB7F388FA59EFAE /* JXSegmentedView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = JXSegmentedView.release.xcconfig; sourceTree = ""; }; + CD9608BE5DC94C79C3B80889048CC2C9 /* ConstraintDirectionalInsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDirectionalInsetTarget.swift; path = Sources/ConstraintDirectionalInsetTarget.swift; sourceTree = ""; }; + CEA9547D3BA26D287FEB73F3765D4196 /* Reachability-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Reachability-dummy.m"; sourceTree = ""; }; + CF39A9D25C909159253007F3C2573326 /* JXPagingView-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXPagingView-umbrella.h"; sourceTree = ""; }; + D0A63046EDC8D9C3318D3B5C0B3193AB /* JXSegmentedNumberItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedNumberItemModel.swift; path = Sources/Number/JXSegmentedNumberItemModel.swift; sourceTree = ""; }; + D0A946002663B961D8830937B5D6C506 /* JXSegmentedIndicatorRainbowLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorRainbowLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorRainbowLineView.swift; sourceTree = ""; }; + D0B8C3FB964677C8073E6FF156C5DBAE /* JXSegmentedDotItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedDotItemModel.swift; path = Sources/Dot/JXSegmentedDotItemModel.swift; sourceTree = ""; }; + D0D9D13FC61F185D3F12CC9AB311971C /* JXSegmentedTitleGradientItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleGradientItemModel.swift; path = Sources/TitleGradient/JXSegmentedTitleGradientItemModel.swift; sourceTree = ""; }; + D284C6B097BA5EF23058D298D08850A9 /* FreeStreamer-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FreeStreamer-prefix.pch"; sourceTree = ""; }; + D33BEC74FB7324EE93DBBB16CA1C2820 /* Validation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Validation.swift; path = Source/Features/Validation.swift; sourceTree = ""; }; + D34FBEA0F135C4262CAB70AF8ED88B0D /* RelativeFormatter+Style.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "RelativeFormatter+Style.swift"; path = "Sources/SwiftDate/Formatters/RelativeFormatter/RelativeFormatter+Style.swift"; sourceTree = ""; }; + D3A75B3E2D24B0E349B5094ABCB55CDC /* JXSegmentedIndicatorProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorProtocol.swift; path = Sources/Indicator/JXSegmentedIndicatorProtocol.swift; sourceTree = ""; }; + D3FB29E068A7B8DC04B1F093AFD6EC83 /* Tiercel.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Tiercel.modulemap; sourceTree = ""; }; + D5C7A07FB940535B0ACB2DA9751FB827 /* MJRefreshBackNormalFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackNormalFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.m; sourceTree = ""; }; + D603B15A4E766FC333BB15832FCEFE56 /* id3_parser.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = id3_parser.h; path = FreeStreamer/FreeStreamer/id3_parser.h; sourceTree = ""; }; + D61195B0A03C35A1089FEF5FA4E466DB /* JXSegmentedIndicatorTriangleView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorTriangleView.swift; path = Sources/Indicator/JXSegmentedIndicatorTriangleView.swift; sourceTree = ""; }; + D6DD1DF6883602FF4485A0854AAFB608 /* HTTPHeaders.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HTTPHeaders.swift; path = Source/Core/HTTPHeaders.swift; sourceTree = ""; }; + D78E1767DF6AFE67C373D115418CF336 /* SwiftDate-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SwiftDate-dummy.m"; sourceTree = ""; }; + D7B9BF1E5F3DBD4B648391B867DC65F5 /* JXSegmentedTitleAttributeItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleAttributeItemModel.swift; path = Sources/AttributeTitle/JXSegmentedTitleAttributeItemModel.swift; sourceTree = ""; }; + D7C2DC64431AFFDEAB367A051F329841 /* ConstraintInsetTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintInsetTarget.swift; path = Sources/ConstraintInsetTarget.swift; sourceTree = ""; }; + D7F6101931EF4D62C239843C57A2C791 /* langs */ = {isa = PBXFileReference; includeInIndex = 1; name = langs; path = Sources/SwiftDate/Formatters/RelativeFormatter/langs; sourceTree = ""; }; + D810D4BC558D69052E21351222B3AF83 /* JXSegmentedTitleImageItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleImageItemModel.swift; path = Sources/TitleImage/JXSegmentedTitleImageItemModel.swift; sourceTree = ""; }; + D8447CAFBD6F721656250A2409FCA240 /* Constraint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Constraint.swift; path = Sources/Constraint.swift; sourceTree = ""; }; + D8AD190CC05A72112FDFB974A611F803 /* IQTitleBarButtonItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQTitleBarButtonItem.swift; path = IQKeyboardManagerSwift/IQToolbar/IQTitleBarButtonItem.swift; sourceTree = ""; }; + D92259D7A615489F87C16B8B319B2824 /* FileChecksumHelper.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FileChecksumHelper.swift; path = Sources/Utility/FileChecksumHelper.swift; sourceTree = ""; }; + D95580228CC0BEE1E0AFD5C45A81D41A /* OperationQueue+DispatchQueue.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "OperationQueue+DispatchQueue.swift"; path = "Sources/Extensions/OperationQueue+DispatchQueue.swift"; sourceTree = ""; }; + D95BC8DF257D6DF9215BC495770135ED /* http_stream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = http_stream.h; path = FreeStreamer/FreeStreamer/http_stream.h; sourceTree = ""; }; + DA2A9ABE6AB044A8A75E993DD7A7D429 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; + DB06B0B50D662836095FC9FBDA523C32 /* JXSegmentedIndicatorGradientView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorGradientView.swift; path = Sources/Indicator/JXSegmentedIndicatorGradientView.swift; sourceTree = ""; }; + DB0C913B6259067973CC83E161BC902F /* IQKeyboardManager+Toolbar.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Toolbar.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Toolbar.swift"; sourceTree = ""; }; + DB824843A0F095DB5E82D895A8407A4F /* UIButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "UIButton+Kingfisher.swift"; path = "Sources/Extensions/UIButton+Kingfisher.swift"; sourceTree = ""; }; + DBD68AAF67BB25B9E1F44519178DAE0F /* Tiercel */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Tiercel; path = Tiercel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC06AEF1EA119A09D38780349CDA2167 /* ResourceBundle-Kingfisher-Kingfisher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Kingfisher-Kingfisher-Info.plist"; sourceTree = ""; }; + DCA1B564614FA5892379C63D94F871C4 /* DiskStorage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DiskStorage.swift; path = Sources/Cache/DiskStorage.swift; sourceTree = ""; }; + DD37EFEDECC05ECBBA0B449990EF7C0F /* NSTextAttachment+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSTextAttachment+Kingfisher.swift"; path = "Sources/Extensions/NSTextAttachment+Kingfisher.swift"; sourceTree = ""; }; + DD850529AEAAF1F52C7286B5088D828B /* NSButton+Kingfisher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSButton+Kingfisher.swift"; path = "Sources/Extensions/NSButton+Kingfisher.swift"; sourceTree = ""; }; + DD914D71D367DE71160B074235049829 /* IQKeyboardManagerConstantsInternal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardManagerConstantsInternal.swift; path = IQKeyboardManagerSwift/Constants/IQKeyboardManagerConstantsInternal.swift; sourceTree = ""; }; + DF7965FCBF7F70C88E24B4951BB1A7C6 /* RedirectHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RedirectHandler.swift; path = Sources/Networking/RedirectHandler.swift; sourceTree = ""; }; + DFC89BE171DE7E648C53797695D8A220 /* Reachability-Reachability_Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "Reachability-Reachability_Privacy"; path = Reachability_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + E05B0E4ECA120B98E41CFF3E9BAF9A59 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = IQKeyboardManagerSwift/PrivacyInfo.xcprivacy; sourceTree = ""; }; + E0A2F06B87F663A92FCC6AFF0B47689F /* KF.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KF.swift; path = Sources/General/KF.swift; sourceTree = ""; }; + E0F4F267A6894DD903B619D88DD4B281 /* RequestModifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestModifier.swift; path = Sources/Networking/RequestModifier.swift; sourceTree = ""; }; + E18B8DB3629DE5ABA672D2EA056DA4FD /* FSParseRssPodcastFeedRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSParseRssPodcastFeedRequest.h; path = FreeStreamer/FreeStreamer/FSParseRssPodcastFeedRequest.h; sourceTree = ""; }; + E24AC0CBB36F897360D002FAF35B4331 /* AVAssetImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AVAssetImageDataProvider.swift; path = Sources/General/ImageSource/AVAssetImageDataProvider.swift; sourceTree = ""; }; + E2D2C5AD1222F2083B9740B6B843B0E0 /* DateComponents+Extras.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateComponents+Extras.swift"; path = "Sources/SwiftDate/Foundation+Extras/DateComponents+Extras.swift"; sourceTree = ""; }; + E3907D2AE7D5DAB04782E32C936D100E /* MJRefreshAutoStateFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoStateFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m; sourceTree = ""; }; + E3D57D0843F4B1BD3133341EF6C971DA /* Reachability.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Reachability.release.xcconfig; sourceTree = ""; }; + E3E54AA8ABB092B11F44C7EAFB4A20B2 /* http_stream.cpp */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = http_stream.cpp; path = FreeStreamer/FreeStreamer/http_stream.cpp; sourceTree = ""; }; + E41D2A7A53A251B9A384116E9720D856 /* Request.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Core/Request.swift; sourceTree = ""; }; E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = MJRefresh; path = MJRefresh.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E54FE0BF84BDF5E90E2CC8A3B504BE3D /* file_output.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = file_output.h; path = FreeStreamer/FreeStreamer/file_output.h; sourceTree = ""; }; + E5B5329122F0A45061FDFBD241C1F414 /* UploadRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UploadRequest.swift; path = Source/Core/UploadRequest.swift; sourceTree = ""; }; + E5E5AF2E02A2D523608B68D4601F2553 /* RequestCompression.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RequestCompression.swift; path = Source/Features/RequestCompression.swift; sourceTree = ""; }; + E5F270A035F8B56FC159D8CF94EAAEE3 /* SVRadialGradientLayer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVRadialGradientLayer.m; path = SVProgressHUD/SVRadialGradientLayer.m; sourceTree = ""; }; E623870FC6E557F7D90E41BF1892B184 /* Pods-MusicPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MusicPlayer.debug.xcconfig"; sourceTree = ""; }; - E652FB17C57B0AF0273621B2B037A0C7 /* JXSegmentedTitleDataSource.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleDataSource.swift; path = Sources/Title/JXSegmentedTitleDataSource.swift; sourceTree = ""; }; - E681F9A4BFBE0245493029CEB1B8A1C8 /* StringEncoding+Alamofire.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "StringEncoding+Alamofire.swift"; path = "Source/Extensions/StringEncoding+Alamofire.swift"; sourceTree = ""; }; - E6E279BCC1C2C3A2FA64E3E7F3FD5335 /* DataRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataRequest.swift; path = Source/Core/DataRequest.swift; sourceTree = ""; }; - E75F2A56E77879A823734237F7B0D90B /* Runtime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Runtime.swift; path = Sources/Utility/Runtime.swift; sourceTree = ""; }; - E770B62A86FAE28308923E8266E15451 /* MJRefreshBackStateFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshBackStateFooter.m; path = MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m; sourceTree = ""; }; - E88C5C3FE92321767662E35C421964B3 /* Debugging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Debugging.swift; path = Sources/Debugging.swift; sourceTree = ""; }; - E8DAC31F0F6FE66D2C8AF5489F272CEA /* KingfisherManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KingfisherManager.swift; path = Sources/General/KingfisherManager.swift; sourceTree = ""; }; - E9045FA6D60732F49E7765B398DDC46B /* IQUIView+Hierarchy.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIView+Hierarchy.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIView+Hierarchy.swift"; sourceTree = ""; }; - E90BED91E412505AF7055D21B806502E /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = MJRefresh/PrivacyInfo.xcprivacy; sourceTree = ""; }; - E95438C214C29A3893A1B32DE8B1898F /* JXSegmentedTitleAttributeCell.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleAttributeCell.swift; path = Sources/AttributeTitle/JXSegmentedTitleAttributeCell.swift; sourceTree = ""; }; + E648B07B2374E25354F7F0A3630DB640 /* JXSegmentedBaseItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedBaseItemModel.swift; path = Sources/Core/JXSegmentedBaseItemModel.swift; sourceTree = ""; }; + E7535AABBE9FF3A343D71B790DF34BE4 /* Reachability.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; + E782ECBFE74BC5E7C7A69C7C3D33C4FD /* Locales.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Locales.swift; path = Sources/SwiftDate/Supports/Locales.swift; sourceTree = ""; }; + E79F8ECDA1859613039CD7132B7282E8 /* UIScrollView+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIScrollView+MJRefresh.m"; path = "MJRefresh/UIScrollView+MJRefresh.m"; sourceTree = ""; }; + E7EBBE4156E02FC8F034F0C0A10E31B0 /* SnapKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SnapKit.release.xcconfig; sourceTree = ""; }; + E81D72C68E0B3FBF0A135880692E6E33 /* CachedResponseHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CachedResponseHandler.swift; path = Source/Features/CachedResponseHandler.swift; sourceTree = ""; }; + E8854C6D6484D2C5D41527241EA8BCC7 /* ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist"; sourceTree = ""; }; + E978BF5057345D65521CCBC6E7B4D394 /* Storage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Storage.swift; path = Sources/Cache/Storage.swift; sourceTree = ""; }; E97D43C46A45EE515A4DA3AF94398441 /* SVProgressHUD */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SVProgressHUD; path = SVProgressHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E9B6DF66CC0D9538F8F45AB7F7F82FB3 /* ImageDataProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageDataProcessor.swift; path = Sources/Networking/ImageDataProcessor.swift; sourceTree = ""; }; - E9B7826E0EF5E37E1704D27E7C361C78 /* Concurrency.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Concurrency.swift; path = Source/Features/Concurrency.swift; sourceTree = ""; }; - E9C19F89F8FC7DDEB64DB778A90CC2BC /* ConstraintView+Extensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "ConstraintView+Extensions.swift"; path = "Sources/ConstraintView+Extensions.swift"; sourceTree = ""; }; - E9C22A64A41E73EF4590DC6552E80DE8 /* JXPagingView-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "JXPagingView-prefix.pch"; sourceTree = ""; }; - E9CA1CB30F953F8E32CCE0D206E0DFE5 /* ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; sourceTree = ""; }; - EAD5CEA5D178799C659489E705F0BDE8 /* URLConvertible+URLRequestConvertible.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URLConvertible+URLRequestConvertible.swift"; path = "Source/Core/URLConvertible+URLRequestConvertible.swift"; sourceTree = ""; }; - EB8D6628308EE28230C57E4CE1063A31 /* Request.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Request.swift; path = Source/Core/Request.swift; sourceTree = ""; }; - EC02376976FBCAAD8506234104E8BAA5 /* Alamofire-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Alamofire-Info.plist"; sourceTree = ""; }; + EA0A49AFC87CB13825318FB69E4F3964 /* TimePeriodCollection.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodCollection.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodCollection.swift; sourceTree = ""; }; + EA6BEFA8047C293D703F2D4271C95AF4 /* JXSegmentedIndicatorImageView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorImageView.swift; path = Sources/Indicator/JXSegmentedIndicatorImageView.swift; sourceTree = ""; }; + EB13925982168A575D343E753CD1070B /* DisplayLink.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DisplayLink.swift; path = Sources/Utility/DisplayLink.swift; sourceTree = ""; }; + EB4351FC3305AC9C5E1A62A27449DDD5 /* IQUIScrollView+Additions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQUIScrollView+Additions.swift"; path = "IQKeyboardManagerSwift/Categories/IQUIScrollView+Additions.swift"; sourceTree = ""; }; + EB6BC0B4BBD642B6ACD05F64DBC1914A /* MJRefreshGifHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshGifHeader.h; path = MJRefresh/Custom/Header/MJRefreshGifHeader.h; sourceTree = ""; }; + EBB6A03D1FED0558B0BA55987FF67D67 /* SVProgressAnimatedView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SVProgressAnimatedView.m; path = SVProgressHUD/SVProgressAnimatedView.m; sourceTree = ""; }; + EC171B63D94CE51CC1D237A219839672 /* LayoutConstraint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayoutConstraint.swift; path = Sources/LayoutConstraint.swift; sourceTree = ""; }; + EC2449762D441F345F1C4B12A439D080 /* FSParsePlaylistRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FSParsePlaylistRequest.m; path = FreeStreamer/FreeStreamer/FSParsePlaylistRequest.m; sourceTree = ""; }; + EC76090E209131CC4AED42CFFCFEB4BA /* IQPreviousNextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQPreviousNextView.swift; path = IQKeyboardManagerSwift/IQToolbar/IQPreviousNextView.swift; sourceTree = ""; }; + EC85421228E0616F4EDD11D8427A8E09 /* MJRefreshAutoStateFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshAutoStateFooter.h; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.h; sourceTree = ""; }; + ECA15F6CC688D6CF6096FEAB9906F86F /* IQKeyboardReturnKeyHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQKeyboardReturnKeyHandler.swift; path = IQKeyboardManagerSwift/IQKeyboardReturnKeyHandler.swift; sourceTree = ""; }; EDBCA147FB2F16410EABF574FBB6C2EB /* Pods-MusicPlayer-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-MusicPlayer-Info.plist"; sourceTree = ""; }; - EEC2D820ED623D3C956582179E970607 /* IQPlaceholderable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQPlaceholderable.swift; path = IQKeyboardManagerSwift/IQTextView/IQPlaceholderable.swift; sourceTree = ""; }; - EF651BDF14D5A324318BC3F8C317F57A /* Result.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Result.swift; path = Sources/Utility/Result.swift; sourceTree = ""; }; - EFE78C092A2B391D6411B2D75E0897C5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = IQKeyboardManagerSwift/PrivacyInfo.xcprivacy; sourceTree = ""; }; - F088EAD83DD6725075FBD8403594C429 /* ConstraintMakerEditable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintMakerEditable.swift; path = Sources/ConstraintMakerEditable.swift; sourceTree = ""; }; - F0917B962F89415FDAAA876040A72E83 /* DateRepresentable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DateRepresentable.swift; path = Sources/SwiftDate/DateRepresentable.swift; sourceTree = ""; }; - F0CF839DB998A3AA0D5D01C635E92AB8 /* UIScrollView+MJExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+MJExtension.h"; path = "MJRefresh/UIScrollView+MJExtension.h"; sourceTree = ""; }; + EDD206CF9334FE1FE5774C8F2948BF37 /* MJRefreshConfig.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshConfig.m; path = MJRefresh/MJRefreshConfig.m; sourceTree = ""; }; + EDF965E29599DE698FC4901EB4D10794 /* IQKeyboardManager+Debug.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "IQKeyboardManager+Debug.swift"; path = "IQKeyboardManagerSwift/IQKeyboardManager+Debug.swift"; sourceTree = ""; }; + EF4708D4F1BBD4A3AFC205C27DD2D187 /* Tiercel-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Tiercel-umbrella.h"; sourceTree = ""; }; + EF6382B651E5176B0926F7272EA2DE2F /* JXPagingListContainerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingListContainerView.swift; path = Sources/JXPagingView/JXPagingListContainerView.swift; sourceTree = ""; }; + EFD33B818F9C65EE9EE5A7D1F801EC52 /* JXSegmentedView.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = JXSegmentedView.modulemap; sourceTree = ""; }; F0F27AFF60F54ECC48396ECBB22D94EC /* Pods-MusicPlayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-MusicPlayer.release.xcconfig"; sourceTree = ""; }; - F125D521A9DA47CB8D38C8C03AEF68A0 /* JXSegmentedIndicatorLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorLineView.swift; sourceTree = ""; }; - F15F5BABB5166FF00A9E43E67C060454 /* MJRefreshBackNormalFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackNormalFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.h; sourceTree = ""; }; + F1232C6C8ADF2B7CC22FD973A4687B48 /* ImageProcessor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageProcessor.swift; path = Sources/Image/ImageProcessor.swift; sourceTree = ""; }; + F15F1937658AB4207DC3E99E1ABF8729 /* ExtensionHelpers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExtensionHelpers.swift; path = Sources/Utility/ExtensionHelpers.swift; sourceTree = ""; }; F23C669C0B769DC30F5A05CE45FEA445 /* Pods-MusicPlayer */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-MusicPlayer"; path = Pods_MusicPlayer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F3AC7EE5F91C56B1C96124A3C58154A5 /* ResourceBundle-Kingfisher-Kingfisher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Kingfisher-Kingfisher-Info.plist"; sourceTree = ""; }; - F3EEB78584831D0B242BDCAA90CED58E /* KFImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KFImage.swift; path = Sources/SwiftUI/KFImage.swift; sourceTree = ""; }; - F43F88826BD8118C95446B4D9D4F416F /* AVAssetImageDataProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AVAssetImageDataProvider.swift; path = Sources/General/ImageSource/AVAssetImageDataProvider.swift; sourceTree = ""; }; - F44F0D93CFD9BB7C99C0FFCD686FF4E3 /* TimePeriodGroup.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimePeriodGroup.swift; path = Sources/SwiftDate/TimePeriod/Groups/TimePeriodGroup.swift; sourceTree = ""; }; - F4C866B997568C25289AC4481D960976 /* JXPagingView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXPagingView.swift; path = Sources/JXPagingView/JXPagingView.swift; sourceTree = ""; }; - F4ED2B675426F9C1C129D85C5356428E /* SwiftDate.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwiftDate.modulemap; sourceTree = ""; }; - F56A78070D28571910937BC0EE5894B8 /* ISOFormatter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ISOFormatter.swift; path = Sources/SwiftDate/Formatters/ISOFormatter.swift; sourceTree = ""; }; - F62422400EE70C424F8B9090F58835FC /* Kingfisher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Kingfisher-Info.plist"; sourceTree = ""; }; - F6D3AF216BBC3443539D5BAD7BE5DEDF /* SVRadialGradientLayer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SVRadialGradientLayer.h; path = SVProgressHUD/SVRadialGradientLayer.h; sourceTree = ""; }; - F796615F5FF6E91238484B197D6CF227 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; - F7B2E649574AA0FAECB00E9B883C49A6 /* LayoutConstraintItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayoutConstraintItem.swift; path = Sources/LayoutConstraintItem.swift; sourceTree = ""; }; - F86ED01EB7CC70315E585984B7CEADA2 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; - F8C8C3813F3BA6EEC658167AE65D9419 /* DataStreamRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DataStreamRequest.swift; path = Source/Core/DataStreamRequest.swift; sourceTree = ""; }; - FA38A01E090C908AE5886021034EE11D /* Region.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Region.swift; path = Sources/SwiftDate/DateInRegion/Region.swift; sourceTree = ""; }; - FCA08BA243343C7A9EAEE567CC5F4318 /* IQInvocation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IQInvocation.swift; path = IQKeyboardManagerSwift/IQToolbar/IQInvocation.swift; sourceTree = ""; }; - FD0AE0ACE265AEC714180F643F48C621 /* ConstraintAttributes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintAttributes.swift; path = Sources/ConstraintAttributes.swift; sourceTree = ""; }; - FDD5BF7F25804ED6AC6B15D6801FE4AD /* JXSegmentedTitleOrImageItemModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedTitleOrImageItemModel.swift; path = Sources/TitleOrImage/JXSegmentedTitleOrImageItemModel.swift; sourceTree = ""; }; - FDE736DCB96A4842E66D08021E34D36C /* JXSegmentedAnimator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedAnimator.swift; path = Sources/Common/JXSegmentedAnimator.swift; sourceTree = ""; }; - FE5DD88C594D20A1F18F99828A3FEB64 /* ImageBinder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageBinder.swift; path = Sources/SwiftUI/ImageBinder.swift; sourceTree = ""; }; - FEC00130945EBD53372EFBC22FB8119E /* JXSegmentedIndicatorParams.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorParams.swift; path = Sources/Indicator/JXSegmentedIndicatorParams.swift; sourceTree = ""; }; - FEE5906F519651F94E70B28F42C4C70B /* LayoutConstraint.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayoutConstraint.swift; path = Sources/LayoutConstraint.swift; sourceTree = ""; }; - FF512DBF4BC4483F9F306E2B3A5CDB70 /* DateComponents+Extras.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "DateComponents+Extras.swift"; path = "Sources/SwiftDate/Foundation+Extras/DateComponents+Extras.swift"; sourceTree = ""; }; + F28F397A75CD65DF0BABDEAF6E23EAF6 /* SessionConfiguration.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SessionConfiguration.swift; path = Sources/General/SessionConfiguration.swift; sourceTree = ""; }; + F296EA654D6F6D3FB04159DDDE772808 /* ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; sourceTree = ""; }; + F2A555308B4D824394609780FA1D1CD6 /* MJRefresh.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.release.xcconfig; sourceTree = ""; }; + F2D1BE90941DDDB05B7D26E18A88B5C8 /* MJRefreshBackStateFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackStateFooter.h; path = MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.h; sourceTree = ""; }; + F4020F6C1FB0BB6A58FC49D5FEFB7454 /* Alamofire.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Alamofire.release.xcconfig; sourceTree = ""; }; + F4B34772DCB703B04668370DA984408B /* UIScrollView+MJExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIScrollView+MJExtension.h"; path = "MJRefresh/UIScrollView+MJExtension.h"; sourceTree = ""; }; + F5B640A3A5A56570BD6D1B5693A7C4D8 /* Tiercel-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Tiercel-dummy.m"; sourceTree = ""; }; + F6F1C5A009876A142269C61CC4EC719C /* Date+Compare.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Compare.swift"; path = "Sources/SwiftDate/Date/Date+Compare.swift"; sourceTree = ""; }; + F78B502A2849402E0A52A4D175507A77 /* ConstraintConstantTarget.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintConstantTarget.swift; path = Sources/ConstraintConstantTarget.swift; sourceTree = ""; }; + F8DE10CA8E086617945517225C8F1E89 /* MJRefreshBackFooter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = MJRefreshBackFooter.h; path = MJRefresh/Base/MJRefreshBackFooter.h; sourceTree = ""; }; + F92F1C9075944A510D4E5A9C696355AB /* Double+TaskInfo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Double+TaskInfo.swift"; path = "Sources/Extensions/Double+TaskInfo.swift"; sourceTree = ""; }; + F98723A48A6B9BA3B70185B530DBD6C9 /* ConstraintDescription.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ConstraintDescription.swift; path = Sources/ConstraintDescription.swift; sourceTree = ""; }; + FA3B80B6F832FECE2F97FD046F117394 /* EventMonitor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EventMonitor.swift; path = Source/Features/EventMonitor.swift; sourceTree = ""; }; + FA6C3A65958047C1AA995415E6FDD3C6 /* FileManager+AvailableCapacity.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "FileManager+AvailableCapacity.swift"; path = "Sources/Extensions/FileManager+AvailableCapacity.swift"; sourceTree = ""; }; + FB032731F90E0191116E810777820F0B /* Response.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Response.swift; path = Source/Core/Response.swift; sourceTree = ""; }; + FBC0854991635391659B1DA8A0A98CC9 /* Source.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Source.swift; path = Sources/General/ImageSource/Source.swift; sourceTree = ""; }; + FBD733C769524A4890D8CA222061294F /* Common.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Common.swift; path = Sources/General/Common.swift; sourceTree = ""; }; + FCC89394EB57F40E775111D40AA7DB95 /* JXSegmentedIndicatorLineView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = JXSegmentedIndicatorLineView.swift; path = Sources/Indicator/JXSegmentedIndicatorLineView.swift; sourceTree = ""; }; + FCC96C4E503A8275C142ABAFD3CE0373 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; }; + FEFADF03AF0101330230B2A77AA48B0B /* FSXMLHttpRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FSXMLHttpRequest.h; path = FreeStreamer/FreeStreamer/FSXMLHttpRequest.h; sourceTree = ""; }; + FF0B549F09E6F39720F2622AA415F744 /* SVProgressHUD.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SVProgressHUD.debug.xcconfig; sourceTree = ""; }; + FF22CC8BEABDCD1C073ACB08C8C0EFE3 /* GIFAnimatedImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GIFAnimatedImage.swift; path = Sources/Image/GIFAnimatedImage.swift; sourceTree = ""; }; + FF2F8D181C6E23DFEAF1BC524C922FAB /* Date+Components.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Date+Components.swift"; path = "Sources/SwiftDate/Date/Date+Components.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 067799593B2F15037103C952D8E15BCA /* Frameworks */ = { + 0176F5222C51F523E3F8210910C4E1D2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9687320638B5AF16C99AF038C957BB76 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 10AD47F1D994001BE8E93F3858924E62 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -927,6 +1133,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1A0070C3094ABBC8B19356861CD6987D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2560AEF01B4ED19DB7D7B4882A6F90F9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -936,6 +1149,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 2824AFBC58BFB120AEB0EDF752307998 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 20F437D81954DB9DF1BA404A1C48EE5D /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 367C517E15190089AC7305B1526B6DF0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3FA6BB0E255F3D1C771079154343333B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -944,14 +1172,7 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 402E49A24B937179BD48FB96001CBF9D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B885211E9E65DAB21AAD17EBC56C6F1 /* Frameworks */ = { + 447E1A1E9C78F7208DEC003469420ABA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -969,6 +1190,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 6682ECE532E9F101074E2B2B437D7691 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 6F796DCACF7FECDF5C5CEF6AFE7BC0FF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -986,11 +1214,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 8CB5E1A5B008E0176BA7B33578DFF0C8 /* Frameworks */ = { + 8FC306C8C914745906B745575E0B8896 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 344BCC1443F520E4175B39B77000D2C1 /* AudioToolbox.framework in Frameworks */, + 7C394A568A5D145C94DB767030F96751 /* AVFoundation.framework in Frameworks */, + 74C30F8C1024E47EB601FF5FE1B05C54 /* CFNetwork.framework in Frameworks */, + AE297D4EC616170B7A43E28DD30D7556 /* Foundation.framework in Frameworks */, + 640928DFF857276867C29DF16DF95B0B /* MediaPlayer.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9F5E232476A9F34AB6A6FE7583DD89E7 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 218C0927BB7605141B611529DD6F65B4 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1003,21 +1242,7 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - ACBA24352126BCD74C702B7475807D88 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - E74997FAADCF49681E528FCB21A4D1AA /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - E77A06A0307FF511269865599FB0CBC8 /* Frameworks */ = { + DA261BE58D4355C7F4D7669AF3110431 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -1032,129 +1257,81 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FC6EA1D19142FAE0240219204EB1591D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC5585CD93EAB3E448D3DD3E27576173 /* Foundation.framework in Frameworks */, + 9571E1CF11BE724718E87ECBB448A146 /* SystemConfiguration.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 06AE3DF2432EBA8876A90CEDD3312552 /* Resources */ = { + 0C1564B7298817050E59A8DC22616ADA /* Support Files */ = { isa = PBXGroup; children = ( - F796615F5FF6E91238484B197D6CF227 /* PrivacyInfo.xcprivacy */, - ); - name = Resources; - sourceTree = ""; - }; - 0D71198B2379509AC4AA8EC9853ED524 /* Support Files */ = { - isa = PBXGroup; - children = ( - CF14834E63FED3D59E4631FEEA9514CD /* MJRefresh.modulemap */, - 5DD688DF2ADF79B72ECC735C35F05F41 /* MJRefresh-dummy.m */, - 5AEFD27CAFBA0B3BB80B6F22588271D1 /* MJRefresh-Info.plist */, - ADD37EAEC18687AE8ED7C2EED0B1463A /* MJRefresh-prefix.pch */, - 82E922355D2129F96245BA8E4E5FFF67 /* MJRefresh-umbrella.h */, - 3AE4A701F7BAB983ED11B504E6487F76 /* MJRefresh.debug.xcconfig */, - AC5E8113942F4B6D5C87580AA549D92C /* MJRefresh.release.xcconfig */, - 4DE64A8F508B88F991A85594504A5076 /* ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist */, + 09BA7275A4B51D06CE96633FFCFEB8C7 /* ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist */, + BAD7B276B5B5105C81921E0B4E88DF8C /* SnapKit.modulemap */, + C48D633A21657F503CDC4FC9CE8A949E /* SnapKit-dummy.m */, + A7CEF4C6C4AFAC58F97E11BC9EA0484D /* SnapKit-Info.plist */, + 6B6FA226D0E99F388099E2762F2A0DB0 /* SnapKit-prefix.pch */, + 77DB14F123949BD939D3F699B4266D69 /* SnapKit-umbrella.h */, + 60917B37B15D85EBE318A3B375752857 /* SnapKit.debug.xcconfig */, + E7EBBE4156E02FC8F034F0C0A10E31B0 /* SnapKit.release.xcconfig */, ); name = "Support Files"; - path = "../Target Support Files/MJRefresh"; + path = "../Target Support Files/SnapKit"; sourceTree = ""; }; - 124E79470B5BB059B6D2B9C11028C2E3 /* Resources */ = { + 15CB84B6F11ADF8A5B340D9E18CBAD9F /* Support Files */ = { isa = PBXGroup; children = ( - C50C61572CB504F3A1B27F44B20F385F /* PrivacyInfo.xcprivacy */, + 2672A89EE6A8AD525224E066A860B77E /* SwiftDate.modulemap */, + D78E1767DF6AFE67C373D115418CF336 /* SwiftDate-dummy.m */, + 65AFBD9AEA9E99F7AC7C95712E97940F /* SwiftDate-Info.plist */, + 316F9447AB3AC7D8F275685D9B863F2B /* SwiftDate-prefix.pch */, + 9C865CCD1F85473AC6AEF3AF846C60A5 /* SwiftDate-umbrella.h */, + AFDB43B2AA4D143F40F21098E3079899 /* SwiftDate.debug.xcconfig */, + 648A2050353718943995F249C37376B6 /* SwiftDate.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/SwiftDate"; + sourceTree = ""; + }; + 1B5C49DB2CAB492BF1D30955C6716144 /* SVProgressHUD */ = { + isa = PBXGroup; + children = ( + D7F882C8D194EAEF8E4226A5EF684777 /* Core */, + A5D0BD97B5C76AC4107C5DF3BFBB0294 /* Support Files */, + ); + name = SVProgressHUD; + path = SVProgressHUD; + sourceTree = ""; + }; + 1DA4D3F249680F28ACC640A644C31F14 /* Resources */ = { + isa = PBXGroup; + children = ( + 882F435FDF3E0CDABD59D7FEF9958329 /* MJRefresh.bundle */, + 367AAD6A6017F88B42A968FCBA8F44A8 /* PrivacyInfo.xcprivacy */, ); name = Resources; sourceTree = ""; }; - 19C29489FB998EB8E7E019A0A9BE7772 /* Products */ = { + 220658A6B5D2AEF26B400C080BA7F437 /* Support Files */ = { isa = PBXGroup; children = ( - 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire */, - 085DBCE7DD98588B2ED103B1C1F36026 /* Alamofire-Alamofire */, - A8E950A16D00F649C54FFB30F81D7842 /* IQKeyboardManagerSwift */, - 2F4A1CCB21DB7EA5A2ACEB11E374FBCA /* JXPagingView */, - 7EB20B4E68CCB69F85E7D08B7F8463D6 /* JXPagingView-JXPagingView */, - 07928762D9A8551470DAAD7C1E1F53A5 /* JXSegmentedView */, - 92B0EC788EDA1B0CFA48DFFCB3DDAECD /* JXSegmentedView-JXSegmentedView */, - C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher */, - C298ABB78D9B05529B89D8322DB2E7B0 /* Kingfisher-Kingfisher */, - E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh */, - 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */, - F23C669C0B769DC30F5A05CE45FEA445 /* Pods-MusicPlayer */, - 979486118B3E90C08386079D57962701 /* SnapKit */, - B9DCB5EC0B1CDADD221717CADDF62359 /* SnapKit-SnapKit_Privacy */, - E97D43C46A45EE515A4DA3AF94398441 /* SVProgressHUD */, - 58AE0544E0C381DDBD09356C357EC82B /* SwiftDate */, + 20D52F5BF32D92327D403383E88D33F4 /* IQKeyboardManagerSwift.modulemap */, + 6EBF85EEDF18C69141A9E1E703C905CD /* IQKeyboardManagerSwift-dummy.m */, + 12F886FAD42295605D07DF85466F716A /* IQKeyboardManagerSwift-Info.plist */, + 3DFFF177D8E56A2AA8900BF72F3D7B10 /* IQKeyboardManagerSwift-prefix.pch */, + 19AD0AEC321066C273D5EC90BC2DF7A9 /* IQKeyboardManagerSwift-umbrella.h */, + 7FA1D7762F6269DB38290C5D3BA1B21A /* IQKeyboardManagerSwift.debug.xcconfig */, + 66BC4906D86D7379576ACE241C5C5B97 /* IQKeyboardManagerSwift.release.xcconfig */, ); - name = Products; - sourceTree = ""; - }; - 21A960E74EC481373E719394FDBE27EF /* Resources */ = { - isa = PBXGroup; - children = ( - 832AF59B8B94B734BFA5598CF822D2E8 /* PrivacyInfo.xcprivacy */, - ); - name = Resources; - sourceTree = ""; - }; - 22B6388EE2F6A689255DDA896BFF58B8 /* MJRefresh */ = { - isa = PBXGroup; - children = ( - 1434E2DF42114AA07FFC65F0AF74B738 /* MJRefresh.h */, - 94F9116CE6A49B5AFFA19B16D5346243 /* MJRefreshAutoFooter.h */, - 7A43EE3F113EE7EB81E5065DB5FDD3F2 /* MJRefreshAutoFooter.m */, - 1624F448C0756E91DC913FD781A9A9EC /* MJRefreshAutoGifFooter.h */, - A10B7F476AA2BC85DA32CDCA695E94CF /* MJRefreshAutoGifFooter.m */, - 6C5F4CF175A8588A2773B4AF89EA51EC /* MJRefreshAutoNormalFooter.h */, - A04C1CC2D7176FD20CE057E555EFC2CB /* MJRefreshAutoNormalFooter.m */, - 7B9379FA3746B5215A31DE8D1CD26166 /* MJRefreshAutoStateFooter.h */, - BE8BB5A012C6C501F12A4876ABF49101 /* MJRefreshAutoStateFooter.m */, - D5A9B7775663040A8BD50618EDF84298 /* MJRefreshBackFooter.h */, - 136FDCF413CE3C1DC00030DAFFF9A94F /* MJRefreshBackFooter.m */, - 1F08C4884167A8DF12652D4ADA87DC8E /* MJRefreshBackGifFooter.h */, - 50DD20A7091C1AF20BBDAE8FF40C638E /* MJRefreshBackGifFooter.m */, - F15F5BABB5166FF00A9E43E67C060454 /* MJRefreshBackNormalFooter.h */, - 1C8F4284CBAD963633A64783E4CFB37D /* MJRefreshBackNormalFooter.m */, - 67853DE27314756744C79CE55FBAA9A4 /* MJRefreshBackStateFooter.h */, - E770B62A86FAE28308923E8266E15451 /* MJRefreshBackStateFooter.m */, - 0082002FD60B11229524482ADA592954 /* MJRefreshComponent.h */, - 4CBB6593899E987A5B4300FC21B80983 /* MJRefreshComponent.m */, - BB17A1319D51982E2F7A99EFC3D11F38 /* MJRefreshConfig.h */, - 813F5F884BA0F0F6C062E545B4CA81EC /* MJRefreshConfig.m */, - 03538AE63A9932FE6DD18C82468E190F /* MJRefreshConst.h */, - 27798DCE9459ACACF37012755F087DB9 /* MJRefreshConst.m */, - 3A99AB9AED98C1B656C310507C966514 /* MJRefreshFooter.h */, - C76A1ABBD38622E820640903E4A979AD /* MJRefreshFooter.m */, - 536595B34971D58E93F91CEFFBA20A22 /* MJRefreshGifHeader.h */, - 70D291797444B210FC11B05604380747 /* MJRefreshGifHeader.m */, - 8124055AC9E04813E53A3EE6EDD5E1DE /* MJRefreshHeader.h */, - 612886EAB1AE5139D8545245115B7EC8 /* MJRefreshHeader.m */, - E4678D7C77B6546A4205C194E9F275EB /* MJRefreshNormalHeader.h */, - 8F1162C00BCBB4CECFD5CA05ED5667F3 /* MJRefreshNormalHeader.m */, - D18ABD1C495B7F72F0284AAC613A93F2 /* MJRefreshNormalTrailer.h */, - 280637DA318DC5D912D98194C6844751 /* MJRefreshNormalTrailer.m */, - 86545830194573BF57878FC631A90181 /* MJRefreshStateHeader.h */, - 82CD43FF64DC36B945840368DAC1706E /* MJRefreshStateHeader.m */, - B764532F7A465FF640FDA97A850A103D /* MJRefreshStateTrailer.h */, - E0E418BB3B470DAB2533C31BAD7884C0 /* MJRefreshStateTrailer.m */, - A56C8749F94C4894BDE28709B22BC8EC /* MJRefreshTrailer.h */, - 13184921F92E5D84095DD92314A9A4EA /* MJRefreshTrailer.m */, - B4111D05E2FD45B47E3E6AD830625F74 /* NSBundle+MJRefresh.h */, - 76BE0C86A979CEA47E0B7AD2EE15D888 /* NSBundle+MJRefresh.m */, - A418AA4ADA9598EFD690FF4427F182A5 /* UICollectionViewLayout+MJRefresh.h */, - 1DA3BDF290E39DAB4D1F114C808EB725 /* UICollectionViewLayout+MJRefresh.m */, - F0CF839DB998A3AA0D5D01C635E92AB8 /* UIScrollView+MJExtension.h */, - 3B4DD5E3C3843A80F1882655B380DCDA /* UIScrollView+MJExtension.m */, - 96222B976672EC3736CB9B53FC49C92E /* UIScrollView+MJRefresh.h */, - D38764AB52ACD239BAD933070283AA4F /* UIScrollView+MJRefresh.m */, - 810BB8FC42B516FF79349CC6C8C13E4C /* UIView+MJExtension.h */, - E3C11DA33AB8B0E9203520516067EC23 /* UIView+MJExtension.m */, - 8F7DDD383A521C01FAB264F2788D6E86 /* Resources */, - 0D71198B2379509AC4AA8EC9853ED524 /* Support Files */, - ); - name = MJRefresh; - path = MJRefresh; + name = "Support Files"; + path = "../Target Support Files/IQKeyboardManagerSwift"; sourceTree = ""; }; 2AC81103547C6D86F33FC7D3D692CCAE /* Pods-MusicPlayer */ = { @@ -1174,30 +1351,220 @@ path = "Target Support Files/Pods-MusicPlayer"; sourceTree = ""; }; - 385385A8F793C6C811AF2F8236997A1B /* JXPagingView */ = { + 2FE445AD883B9ABD3E0241A05A5B79FD /* Frameworks */ = { isa = PBXGroup; children = ( - 86CA141D7FE28EE45ACB71EA520C1024 /* Paging */, - A7A71010D8CCF5C1D9EF378910670798 /* Support Files */, + D4FE3EB2EF4187AD0C43A8B72670BF1E /* iOS */, ); - name = JXPagingView; - path = JXPagingView; + name = Frameworks; sourceTree = ""; }; - 3997AFF09C6B2741C9D946A854C69D26 /* Resources */ = { + 363A010D0E858BA4249C50C9E01F015A /* Resources */ = { isa = PBXGroup; children = ( - F86ED01EB7CC70315E585984B7CEADA2 /* PrivacyInfo.xcprivacy */, + 255E5C87081214F2B68D64E82AA9DD61 /* PrivacyInfo.xcprivacy */, ); name = Resources; sourceTree = ""; }; - 47BE6238B9D2DAEAE874B27AAB7EA08A /* Resources */ = { + 38A071E0A1AE8529E61938044E0BDF36 /* Support Files */ = { isa = PBXGroup; children = ( - B5819E5E132D7A725A5CF03163E7796D /* langs */, + C170E9CBAFE070E5D1B42846B3B96DDA /* MJRefresh.modulemap */, + 979BC6537C00870AA3E990B2726CA8B8 /* MJRefresh-dummy.m */, + 447339956CEEAB15D60D70501BFCF606 /* MJRefresh-Info.plist */, + 6EC99E48058E2E9A34BF98B47999CEA1 /* MJRefresh-prefix.pch */, + 05A3A7AA48C71521A2AB98871FF57BE8 /* MJRefresh-umbrella.h */, + 00A4DA108AE69BEB562DE490822C90D3 /* MJRefresh.debug.xcconfig */, + F2A555308B4D824394609780FA1D1CD6 /* MJRefresh.release.xcconfig */, + F296EA654D6F6D3FB04159DDDE772808 /* ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist */, ); - name = Resources; + name = "Support Files"; + path = "../Target Support Files/MJRefresh"; + sourceTree = ""; + }; + 39E22E3FB9C9F7B02ADD34EA96F52434 /* Kingfisher */ = { + isa = PBXGroup; + children = ( + 0565234423B639A17F1E91A3D559AB99 /* AnimatedImageView.swift */, + 1318FA20C5E765D28786F3F849CDB26E /* AuthenticationChallengeResponsable.swift */, + E24AC0CBB36F897360D002FAF35B4331 /* AVAssetImageDataProvider.swift */, + 3FF6EFC03B1FAE3BFFE25B327C58E984 /* Box.swift */, + C9F8DEB6C11CB3F37C7618260A212B87 /* CacheSerializer.swift */, + 30E74D845B956ED13AF023CCFCFB780E /* CallbackQueue.swift */, + B66B6DFD66EDD8490CD94EC55E8E7B64 /* CPListItem+Kingfisher.swift */, + 87B2754B6551345D199982248BF5F258 /* Delegate.swift */, + DCA1B564614FA5892379C63D94F871C4 /* DiskStorage.swift */, + EB13925982168A575D343E753CD1070B /* DisplayLink.swift */, + F15F1937658AB4207DC3E99E1ABF8729 /* ExtensionHelpers.swift */, + 1AA4BDFF82A93332CC58469234702EB5 /* Filter.swift */, + 0053FAA6FA0A2E35B78413CFFFE3D7DF /* FormatIndicatedCacheSerializer.swift */, + FF22CC8BEABDCD1C073ACB08C8C0EFE3 /* GIFAnimatedImage.swift */, + BE510A9C1FFF14CCF35D6CC112249B7B /* GraphicsContext.swift */, + 9ABA7A1E43832CC4FDC1B0589A531B91 /* Image.swift */, + 1A9FE5CC92E229E62CDC676046989D88 /* ImageBinder.swift */, + 98B283FDE7FACB318CC1398FECAB76E7 /* ImageCache.swift */, + 110F64C4F2FD9C3DE754C0DF6D6BE4CB /* ImageContext.swift */, + 06F5FCDB146537A5FB81A9608C55800C /* ImageDataProcessor.swift */, + 9CD589D0CF5798FFB6E46700B50175E2 /* ImageDataProvider.swift */, + 17471A60400FF41CA4D77544B7817005 /* ImageDownloader.swift */, + 10FFD14C2E11E5F89FF13935005EC4D8 /* ImageDownloaderDelegate.swift */, + 5140A9A1F5487CA7CF613C7220D594D3 /* ImageDrawing.swift */, + BC1880EFAED23577D0DE35F1DF62E176 /* ImageFormat.swift */, + 87FE7DB732EAE613423384F1B5F8C6F1 /* ImageModifier.swift */, + B31511FD256433B92C0FEEE45809A116 /* ImagePrefetcher.swift */, + F1232C6C8ADF2B7CC22FD973A4687B48 /* ImageProcessor.swift */, + 80E6C96CBB660219F7EF7C29135EF5D2 /* ImageProgressive.swift */, + 1BFE07EB5815ED4E3061D694AFE169C5 /* ImageTransition.swift */, + 1B99A8829F856511EC0C2AE7FF086B3E /* ImageView+Kingfisher.swift */, + 84D7701F20B5E52C4FAFC9907793CFA5 /* Indicator.swift */, + E0A2F06B87F663A92FCC6AFF0B47689F /* KF.swift */, + 300F57DE95241E7D9CB929D3B2264440 /* KFAnimatedImage.swift */, + 416332D3DF3451F4A442AC1248F5BEB2 /* KFImage.swift */, + 5F732A9C653F736C5918C2BE6C341FA4 /* KFImageOptions.swift */, + AEAB2E073749D67141BC95939FD1DAF9 /* KFImageProtocol.swift */, + 038B18C5D6DE667EE97D03F020423EDD /* KFImageRenderer.swift */, + 4AEBA9C1E1A64D0D2E56C9ECA1009355 /* KFOptionsSetter.swift */, + CAA09510E6E35CC1A196B0830CF9D6F9 /* Kingfisher.swift */, + 40A5DBCBB01B810B9E309C9CF63DD609 /* KingfisherError.swift */, + 9907E83DA970901BFCAAF3A5FDCAC9ED /* KingfisherManager.swift */, + 22B75C82B4FA45FBD610EE3820F36ABA /* KingfisherOptionsInfo.swift */, + 454DD3800F47E80B70947B50B5EA6B4A /* MemoryStorage.swift */, + DD850529AEAAF1F52C7286B5088D828B /* NSButton+Kingfisher.swift */, + DD37EFEDECC05ECBBA0B449990EF7C0F /* NSTextAttachment+Kingfisher.swift */, + 49619FA43727E6B05EFF845F078B07FF /* Placeholder.swift */, + DF7965FCBF7F70C88E24B4951BB1A7C6 /* RedirectHandler.swift */, + E0F4F267A6894DD903B619D88DD4B281 /* RequestModifier.swift */, + 98AA35E31BC370E094419B4110DEBB61 /* Resource.swift */, + 7469843EF11B7651EAA26E55E112E349 /* Result.swift */, + 8754C6318F74231AB6E36C5F1BD49BDB /* RetryStrategy.swift */, + 19F386566B21015AF7730984275FC385 /* Runtime.swift */, + 56D2D30775656A6F7F644EC3E77AC357 /* SessionDataTask.swift */, + 85BFAD2523C290A22C7977CF6F4A6936 /* SessionDelegate.swift */, + 1C48D497CEEA7EB34E2FC6194741A4F4 /* SizeExtensions.swift */, + FBC0854991635391659B1DA8A0A98CC9 /* Source.swift */, + E978BF5057345D65521CCBC6E7B4D394 /* Storage.swift */, + 64506C7AE6F75CC4AB585A017D14BE73 /* String+MD5.swift */, + 9D6E75DD4281F6901C196B196AC70734 /* TVMonogramView+Kingfisher.swift */, + DB824843A0F095DB5E82D895A8407A4F /* UIButton+Kingfisher.swift */, + 319CEE88A0DEACE6042548814209CC59 /* WKInterfaceImage+Kingfisher.swift */, + CDA3E2AA607A88124CB459491AAC1308 /* Resources */, + D857EE8D3444898A1B112BA3089C7841 /* Support Files */, + ); + name = Kingfisher; + path = Kingfisher; + sourceTree = ""; + }; + 3A3B35A921C15279048CFBB3EBE33F29 /* Alamofire */ = { + isa = PBXGroup; + children = ( + 951109FA965A9BDC4FC4E338EE37DE65 /* AFError.swift */, + 82ABE0C8114FEFA9A19E7474E07C72B1 /* Alamofire.swift */, + 94B85D70EFBE6C79981BAA409C3C02D5 /* AlamofireExtended.swift */, + 104108C5D1C6A09D9C7329B2B430CFE1 /* AuthenticationInterceptor.swift */, + E81D72C68E0B3FBF0A135880692E6E33 /* CachedResponseHandler.swift */, + 73263B2692F575379BC2971651456D08 /* Combine.swift */, + 9C54ECE18F0497523A08897E77463C71 /* Concurrency.swift */, + 76603E85B6156E967FC2F4A6E2651206 /* DataRequest.swift */, + 06FCD1CF754595FC1050F9E2BE4EE7FE /* DataStreamRequest.swift */, + 085EBD55C44E75117740E1AA43932349 /* DispatchQueue+Alamofire.swift */, + 272E28E27BC34E8A01009E5AF1BFF723 /* DownloadRequest.swift */, + FA3B80B6F832FECE2F97FD046F117394 /* EventMonitor.swift */, + D6DD1DF6883602FF4485A0854AAFB608 /* HTTPHeaders.swift */, + 14A44A5FC1159E231EEC8EA3A905A120 /* HTTPMethod.swift */, + 558ADE5127C2037C1373813C4E4DA1D6 /* MultipartFormData.swift */, + 96B33F56346B35C8103DED7E12D113E9 /* MultipartUpload.swift */, + 6F835F11137C679D5CF9597B87B42CEC /* NetworkReachabilityManager.swift */, + BAF56CAE407A1246D8E33A86EEAB1CD0 /* Notifications.swift */, + 27DAAB9A8B0D734A9BBE8E8F4453F9D4 /* OperationQueue+Alamofire.swift */, + BE9B2D284160E9548EBF96125E9B7554 /* ParameterEncoder.swift */, + 27DAB2C811D037E7923BC7DC85E95B24 /* ParameterEncoding.swift */, + 646CA35D7A677037E651FB1DCAF038DA /* Protected.swift */, + 23E6D81A1886132FB59F96D28A386AB9 /* RedirectHandler.swift */, + E41D2A7A53A251B9A384116E9720D856 /* Request.swift */, + E5E5AF2E02A2D523608B68D4601F2553 /* RequestCompression.swift */, + 532AAD5D5F6498A0595431E5809B9229 /* RequestInterceptor.swift */, + 93A9C6C311E69800A3DBB32A5CE9A829 /* RequestTaskMap.swift */, + FB032731F90E0191116E810777820F0B /* Response.swift */, + 883227B7FBBBCE19FA3C9DABB6257BF5 /* ResponseSerialization.swift */, + 873505EB51B12F5BA6246196F84D915B /* Result+Alamofire.swift */, + 5A1C920032B029FBF2CB4D3E422174E9 /* RetryPolicy.swift */, + C80011A076CE73595572EF851C7E6A04 /* ServerTrustEvaluation.swift */, + 77C16D3C7CB62A673F650B8575E7221D /* Session.swift */, + 2D97EB1D469B927CC01C4FEE81A6AB26 /* SessionDelegate.swift */, + 968C21562868FE498570DD48476092E9 /* StringEncoding+Alamofire.swift */, + E5B5329122F0A45061FDFBD241C1F414 /* UploadRequest.swift */, + 186CD6B1C19F3A72817C08755AF2F5A5 /* URLConvertible+URLRequestConvertible.swift */, + 151DB2B13390607CE0EBC6E10AFD1E04 /* URLEncodedFormEncoder.swift */, + 8D50102F684B52D0EC7CEDB9B357C54E /* URLRequest+Alamofire.swift */, + 113456ED5740F97EACFA393659870BA7 /* URLSessionConfiguration+Alamofire.swift */, + D33BEC74FB7324EE93DBBB16CA1C2820 /* Validation.swift */, + 488856EC78D9E7B766DB9E061CABC8F5 /* WebSocketRequest.swift */, + FB350D3C43D0AC138C4F084F04558B91 /* Resources */, + A58482394CEABCE02F0907290AB10F3A /* Support Files */, + ); + name = Alamofire; + path = Alamofire; + sourceTree = ""; + }; + 417E92E0D92975DAAD689C9DAC2BF494 /* Tiercel */ = { + isa = PBXGroup; + children = ( + 89A5C6D1F8C391EC0BA7A85FA520C894 /* Array+Safe.swift */, + 6C3B19C6050B49E5E5B56765AEF5FE5F /* Cache.swift */, + 7E3E285614C475158E65C94AA0A728CD /* CodingUserInfoKey+Cache.swift */, + FBD733C769524A4890D8CA222061294F /* Common.swift */, + A315721095BED3E19F0DA90AB3FB056A /* Data+Hash.swift */, + A5B2767297008EECD4B485DFC38F2111 /* DispatchQueue+Safe.swift */, + F92F1C9075944A510D4E5A9C696355AB /* Double+TaskInfo.swift */, + 96F5AE52581DB823B2E91EEB6FD22AF1 /* DownloadTask.swift */, + 7A0F024C688B9208EE21BA6A338CAF16 /* Executer.swift */, + D92259D7A615489F87C16B8B319B2824 /* FileChecksumHelper.swift */, + FA6C3A65958047C1AA995415E6FDD3C6 /* FileManager+AvailableCapacity.swift */, + 0E00AA56B8E87F888C2272177B0DB376 /* Int64+TaskInfo.swift */, + 85976525DE544B30C96C2119D21D3244 /* Notifications.swift */, + D95580228CC0BEE1E0AFD5C45A81D41A /* OperationQueue+DispatchQueue.swift */, + CB2EF2CB55AE8534BFE107E6DC7FFC4D /* Protected.swift */, + 2DCC525B5FC5BC436F4E144E018923F5 /* ResumeDataHelper.swift */, + F28F397A75CD65DF0BABDEAF6E23EAF6 /* SessionConfiguration.swift */, + 6265DB8E8D0BAED2C39405AECD61AF04 /* SessionDelegate.swift */, + 8AD66F835AAF00EF7D103A4AA5C1F6E4 /* SessionManager.swift */, + 963052BB2E62093DA5CBB64F34A6F258 /* String+Hash.swift */, + 86D907265F596152E9F2094822FF73E4 /* Task.swift */, + 916A56D8864031F453F9C00A745F32D0 /* TiercelError.swift */, + 64647B22D390B0085715A7A6C6432D74 /* URLConvertible.swift */, + 9C6287F6DCA0BCB29F738E6D22B4F9F6 /* URLSession+ResumeData.swift */, + CBE00C6E7CE6EE8D06C3C40111815FC7 /* Support Files */, + ); + name = Tiercel; + path = Tiercel; + sourceTree = ""; + }; + 4811988BB32FB0ED0491A0F2C6DBC90D /* Products */ = { + isa = PBXGroup; + children = ( + 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire */, + 085DBCE7DD98588B2ED103B1C1F36026 /* Alamofire-Alamofire */, + 359F20447DD6B2DABE3B77D75DA92F82 /* FreeStreamer */, + A8E950A16D00F649C54FFB30F81D7842 /* IQKeyboardManagerSwift */, + 2F4A1CCB21DB7EA5A2ACEB11E374FBCA /* JXPagingView */, + 7EB20B4E68CCB69F85E7D08B7F8463D6 /* JXPagingView-JXPagingView */, + 07928762D9A8551470DAAD7C1E1F53A5 /* JXSegmentedView */, + 92B0EC788EDA1B0CFA48DFFCB3DDAECD /* JXSegmentedView-JXSegmentedView */, + C3F44C782D64D7EB20B61CE3844EBFAD /* Kingfisher */, + C298ABB78D9B05529B89D8322DB2E7B0 /* Kingfisher-Kingfisher */, + E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh */, + 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */, + F23C669C0B769DC30F5A05CE45FEA445 /* Pods-MusicPlayer */, + 400FF55D0451E7A8F33A3D0D3E11C1B9 /* Reachability */, + DFC89BE171DE7E648C53797695D8A220 /* Reachability-Reachability_Privacy */, + 979486118B3E90C08386079D57962701 /* SnapKit */, + B9DCB5EC0B1CDADD221717CADDF62359 /* SnapKit-SnapKit_Privacy */, + E97D43C46A45EE515A4DA3AF94398441 /* SVProgressHUD */, + 58AE0544E0C381DDBD09356C357EC82B /* SwiftDate */, + DBD68AAF67BB25B9E1F44519178DAE0F /* Tiercel */, + ); + name = Products; sourceTree = ""; }; 4AD22DFAF5934D8E9D83B59C461B799B /* Targets Support Files */ = { @@ -1208,389 +1575,235 @@ name = "Targets Support Files"; sourceTree = ""; }; - 4ED80A127B880DDBD359D089C93079AC /* Support Files */ = { + 5B4B1252A49F7CFE90EC436D9D9DDD99 /* IQKeyboardManagerSwift */ = { isa = PBXGroup; children = ( - 13AB468987C8A41B4D5D6B895F8B9A6F /* JXSegmentedView.modulemap */, - 356BE641A77A6E56A217190DD89D8259 /* JXSegmentedView-dummy.m */, - 7B24959E1B5D6E0DB944451D77D9BEEF /* JXSegmentedView-Info.plist */, - 3AB6A380647126E36416E4610575F354 /* JXSegmentedView-prefix.pch */, - 37C1098A010D1C8E4E069D976A8D8DFD /* JXSegmentedView-umbrella.h */, - 0DB2B3B9A91393821FCD01D47CB39B40 /* JXSegmentedView.debug.xcconfig */, - 451FEDBFD2A922230CA33D46534CD7F7 /* JXSegmentedView.release.xcconfig */, - 1C8C49DCED4EC3826245D19DFC7FA7E9 /* ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist */, - ); - name = "Support Files"; - path = "../Target Support Files/JXSegmentedView"; - sourceTree = ""; - }; - 5B7171C88905EE86E6FB47E5A6F4D2D4 /* Support Files */ = { - isa = PBXGroup; - children = ( - 5CEFF5B6E9210A0DDADD6C198F0BC68C /* Alamofire.modulemap */, - AA35F74EEBAD4E0CB28C015AD5D6EECF /* Alamofire-dummy.m */, - EC02376976FBCAAD8506234104E8BAA5 /* Alamofire-Info.plist */, - 84C32F4F34E89EC16281BE7CB8022B54 /* Alamofire-prefix.pch */, - 56263B06F3EAEC382F6F91F1599D4E74 /* Alamofire-umbrella.h */, - 5EBCD15895605FBBBCD96B9C8E928D3B /* Alamofire.debug.xcconfig */, - 7BD8D5C9444C4C58CF152CD4CC8DB0A3 /* Alamofire.release.xcconfig */, - CE14F9280BCE30360011E5C9806D2516 /* ResourceBundle-Alamofire-Alamofire-Info.plist */, - ); - name = "Support Files"; - path = "../Target Support Files/Alamofire"; - sourceTree = ""; - }; - 6625D72D58B79130D815A3A6D7072BCE /* iOS */ = { - isa = PBXGroup; - children = ( - AA39C6D2448DC5A9AD18DDA3C96A1A0F /* Accelerate.framework */, - 8A2A128F3FAE37601751167FDC47182A /* CFNetwork.framework */, - 828F1C480D9959D94E2868CE1084677F /* CoreGraphics.framework */, - B4B8063291033C95A8B2A24FA2AD7A79 /* Foundation.framework */, - C07BEF0CE5DECC5BDCAC8625BF2FFA4B /* QuartzCore.framework */, - 1199E580C5F19AFD1010FE20258DADBD /* UIKit.framework */, - ); - name = iOS; - sourceTree = ""; - }; - 6A4EF31B924CC5861010E154A95CAD31 /* Support Files */ = { - isa = PBXGroup; - children = ( - E9CA1CB30F953F8E32CCE0D206E0DFE5 /* ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist */, - 86D05EEFA439B19ACFEC22979C2BBF4D /* SnapKit.modulemap */, - A933F95E4E12D348E925A9E3B27750CB /* SnapKit-dummy.m */, - BB993F1FCBAA91D44D43B0587DB633FB /* SnapKit-Info.plist */, - 77AF4A7E7C61F4843CEE9608EFB1F2A8 /* SnapKit-prefix.pch */, - 31126E0DD1B56D3713965A56F0B05AC8 /* SnapKit-umbrella.h */, - BC90E0F634A75E57580CB83CB0B039E4 /* SnapKit.debug.xcconfig */, - 9DB02FC7AC88AE54321E64548CBFD1FF /* SnapKit.release.xcconfig */, - ); - name = "Support Files"; - path = "../Target Support Files/SnapKit"; - sourceTree = ""; - }; - 86CA141D7FE28EE45ACB71EA520C1024 /* Paging */ = { - isa = PBXGroup; - children = ( - C2DC9D938B1784B3E0E8FFC9851FCAEA /* JXPagingListContainerView.swift */, - 89FA1854F15DFB5CA5EAC2BF88823143 /* JXPagingListRefreshView.swift */, - BB420F181A7222985A450867020EFB2C /* JXPagingMainTableView.swift */, - 142F33BE61F0E752B876C5145BE8D812 /* JXPagingSmoothView.swift */, - F4C866B997568C25289AC4481D960976 /* JXPagingView.swift */, - B6A64081B24DF43D18A38FFA4FCFBDCA /* Resources */, - ); - name = Paging; - sourceTree = ""; - }; - 8B5308B450A10A8C99942BA8C8381883 /* IQKeyboardManagerSwift */ = { - isa = PBXGroup; - children = ( - B860B422FB58A9842214FE11A933BBD4 /* IQBarButtonItem.swift */, - FCA08BA243343C7A9EAEE567CC5F4318 /* IQInvocation.swift */, - B2E886364CE3D1DE31930B53C67EFBEB /* IQKeyboardManager.swift */, - E17C586A0C27A8F810C609FC78419447 /* IQKeyboardManager+Debug.swift */, - 773DD03EC2EC00F685DE0E46779320CC /* IQKeyboardManager+Internal.swift */, - 52CB2CCAA127ADA834B4CCF4686ECE60 /* IQKeyboardManager+OrientationNotification.swift */, - 988FB962B33EFE0451CB5B6094DEAA75 /* IQKeyboardManager+Position.swift */, - CB34508FC3184D3482A1002BEBC16654 /* IQKeyboardManager+Toolbar.swift */, - AAA467CC8E830A4407AEEA9E195B5572 /* IQKeyboardManager+UIKeyboardNotification.swift */, - 07490687930290E2CF12D183E94326BF /* IQKeyboardManager+UITextFieldViewNotification.swift */, - 9A89AE3F00FA615DB614031B7510C78F /* IQKeyboardManagerConstants.swift */, - 398152B547C8B96AD8C376F10BEB5701 /* IQKeyboardManagerConstantsInternal.swift */, - CDCBF5F15899BDBF63D3CC736990E1A5 /* IQKeyboardReturnKeyHandler.swift */, - 7C1E1CFDA9E2D6AECCA91F210C0B6064 /* IQNSArray+Sort.swift */, - EEC2D820ED623D3C956582179E970607 /* IQPlaceholderable.swift */, - 1FF0E1D3C22D93C0C7B2C51A16E17913 /* IQPreviousNextView.swift */, - 4E16F379BC5CA8C2B21212962C6FA430 /* IQTextView.swift */, - 81D659C29A42861AC5A4EF3B9D26DC0B /* IQTitleBarButtonItem.swift */, - 5AA5BD68BC8AE62D30B207A9A089DBE2 /* IQToolbar.swift */, - C54C3E1777728F805F3EFCB27BB703BC /* IQUIScrollView+Additions.swift */, - 56E8C8A30A6C29843067AEFDBDB0DD5E /* IQUITextFieldView+Additions.swift */, - E9045FA6D60732F49E7765B398DDC46B /* IQUIView+Hierarchy.swift */, - DC81E237B242F0601E0FC35F98708DC4 /* IQUIView+IQKeyboardToolbar.swift */, - 40E3CE126BEDA57D7DA923F7BBC49D32 /* IQUIViewController+Additions.swift */, - F56AEB416F118ECBB450F18C0A431806 /* Resources */, - E322A4AAC43BD85F10A108F72EC08C4E /* Support Files */, + AE8226608B94B7947A425E8D90B33C1E /* IQBarButtonItem.swift */, + C2E016699F6BADA8EE18EC03EEAF1CC8 /* IQInvocation.swift */, + AD260DDFBA4FDA84F5C6A25B2CB47470 /* IQKeyboardManager.swift */, + EDF965E29599DE698FC4901EB4D10794 /* IQKeyboardManager+Debug.swift */, + 88000352748C51EE5F5C9F5B89E199A9 /* IQKeyboardManager+Internal.swift */, + A5097DCB7A30413A98441BDB44CF3B14 /* IQKeyboardManager+OrientationNotification.swift */, + 08FA7E2B552FE3E8F4062758D55B2B19 /* IQKeyboardManager+Position.swift */, + DB0C913B6259067973CC83E161BC902F /* IQKeyboardManager+Toolbar.swift */, + 6A7890939418A54F31B5E2A8545372FD /* IQKeyboardManager+UIKeyboardNotification.swift */, + AB2F1CC639A1BFB895317FCBAF22C6CB /* IQKeyboardManager+UITextFieldViewNotification.swift */, + 3DF55BDFE48613A40A1EF055E76F6481 /* IQKeyboardManagerConstants.swift */, + DD914D71D367DE71160B074235049829 /* IQKeyboardManagerConstantsInternal.swift */, + ECA15F6CC688D6CF6096FEAB9906F86F /* IQKeyboardReturnKeyHandler.swift */, + 9A8CE17F7BEFB6AF765222B453E745F0 /* IQNSArray+Sort.swift */, + 4846DC539D38816DBC96DB7BF50FF1CE /* IQPlaceholderable.swift */, + EC76090E209131CC4AED42CFFCFEB4BA /* IQPreviousNextView.swift */, + 1D8316A6A2618957E57AB5B31051B3AC /* IQTextView.swift */, + D8AD190CC05A72112FDFB974A611F803 /* IQTitleBarButtonItem.swift */, + 65814C78FFC0F5199CE49EAC7BFD63EF /* IQToolbar.swift */, + EB4351FC3305AC9C5E1A62A27449DDD5 /* IQUIScrollView+Additions.swift */, + 69708C606EE0513511D8D46B93793A9D /* IQUITextFieldView+Additions.swift */, + BDE49DC44DD670EFE086D67A7A64F08A /* IQUIView+Hierarchy.swift */, + 9F1D98A6ED81F7F2FFE1483C15157751 /* IQUIView+IQKeyboardToolbar.swift */, + C00F9A44E0AE14DC8B34030B2DA760B3 /* IQUIViewController+Additions.swift */, + E4863A643CB900EA579321DC79EE5D85 /* Resources */, + 220658A6B5D2AEF26B400C080BA7F437 /* Support Files */, ); name = IQKeyboardManagerSwift; path = IQKeyboardManagerSwift; sourceTree = ""; }; - 8F7DDD383A521C01FAB264F2788D6E86 /* Resources */ = { + 63CC861A64AA47422317803C69ACD3AE /* Paging */ = { isa = PBXGroup; children = ( - 84FCB0C95F94873F8D87374CE1ACA147 /* MJRefresh.bundle */, - E90BED91E412505AF7055D21B806502E /* PrivacyInfo.xcprivacy */, + EF6382B651E5176B0926F7272EA2DE2F /* JXPagingListContainerView.swift */, + 21B8B7C727D8E6DCB02966F19E4BDF14 /* JXPagingListRefreshView.swift */, + 7ABAC09EFA34B7B44CD9B563DD0D4C54 /* JXPagingMainTableView.swift */, + CB1399E47A9873040FC5D3A42F382917 /* JXPagingSmoothView.swift */, + 967F95BF5036324BE9254F76C8F11590 /* JXPagingView.swift */, + 363A010D0E858BA4249C50C9E01F015A /* Resources */, + ); + name = Paging; + sourceTree = ""; + }; + 8ECBF590DFCCD8FBDB7B8CED6462242A /* Support Files */ = { + isa = PBXGroup; + children = ( + EFD33B818F9C65EE9EE5A7D1F801EC52 /* JXSegmentedView.modulemap */, + 73116E04CFC77FBAC5754FB3135DD50F /* JXSegmentedView-dummy.m */, + 0A52C805625D7F43FD427AAE5059729A /* JXSegmentedView-Info.plist */, + 9F7BC3B5F563719E7C085370A8A3E353 /* JXSegmentedView-prefix.pch */, + 87F341F9F0357ED1D61BA9EB641A433B /* JXSegmentedView-umbrella.h */, + 18969DF6D4B5B1F3CFC962FAC18910BB /* JXSegmentedView.debug.xcconfig */, + CB76AA0035D5AC744AB7F388FA59EFAE /* JXSegmentedView.release.xcconfig */, + E8854C6D6484D2C5D41527241EA8BCC7 /* ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist */, + ); + name = "Support Files"; + path = "../Target Support Files/JXSegmentedView"; + sourceTree = ""; + }; + A0947FFE3F76937AA502C16B337EAA0D /* Resources */ = { + isa = PBXGroup; + children = ( + 203B8403E6E3D7A6813164E42EA97906 /* PrivacyInfo.xcprivacy */, + 9D2626745F360CF15E27E55F46760135 /* SVProgressHUD.bundle */, ); name = Resources; sourceTree = ""; }; - A1102946118376B3CF7E47B1B547A7DC /* Kingfisher */ = { + A58482394CEABCE02F0907290AB10F3A /* Support Files */ = { isa = PBXGroup; children = ( - 8138B9E4A2A6AA8C683562DF69844549 /* AnimatedImageView.swift */, - 1E5CD298A2F13E3902AAA3510FE5C9FD /* AuthenticationChallengeResponsable.swift */, - F43F88826BD8118C95446B4D9D4F416F /* AVAssetImageDataProvider.swift */, - 56CF6C9E5C4416011FAC32CEB31AA525 /* Box.swift */, - 268D0497BC989AF2355C7E9712755278 /* CacheSerializer.swift */, - 6E1CCD164ECD3A398893DB0C3F224494 /* CallbackQueue.swift */, - BDCF0C8D768EFB2E7C6A48EB2256867E /* CPListItem+Kingfisher.swift */, - D49B5FF4D24E46BB7F695E22A9907317 /* Delegate.swift */, - 6D65D8CBF9D74AB85CB305B6D9B87509 /* DiskStorage.swift */, - 7694CB3A399B0E568F03F27D28868B95 /* DisplayLink.swift */, - CFCB35EFBB8A545F2F8352A7D27D5B2A /* ExtensionHelpers.swift */, - 5AAF19FA9EB514B2794ECCAD1BB7FA2E /* Filter.swift */, - CDC741E10FE2F276DD3C474545224A00 /* FormatIndicatedCacheSerializer.swift */, - D394AD7BFA59E7271EC623CF17FBDD30 /* GIFAnimatedImage.swift */, - 1F0A7FCD71890676CE08D3ADDB7CA7B4 /* GraphicsContext.swift */, - 3D613FC602AD1B3823E553095A17BB58 /* Image.swift */, - FE5DD88C594D20A1F18F99828A3FEB64 /* ImageBinder.swift */, - 3CF9BAB200D238346E29ED2DBA085B17 /* ImageCache.swift */, - D061CC1A5FB61D63B44C75541D898A36 /* ImageContext.swift */, - E9B6DF66CC0D9538F8F45AB7F7F82FB3 /* ImageDataProcessor.swift */, - C408D0CD682A652982659893260F5EA4 /* ImageDataProvider.swift */, - DA1E44B32910D0CD9054692D4F938398 /* ImageDownloader.swift */, - 5D36A80F29E20B609E647BAFB548A270 /* ImageDownloaderDelegate.swift */, - 08D053BD683C26F73E7D5BAADC8A4FEF /* ImageDrawing.swift */, - 6CEA2AD00C187BA485EFF154E41EF587 /* ImageFormat.swift */, - D776B0C4D357F0B0466B42C8BF6495D5 /* ImageModifier.swift */, - 36730D770821B0E94A45B2817FC4E2FD /* ImagePrefetcher.swift */, - 662BA990AF2EEE7892AF8EA530C1AE31 /* ImageProcessor.swift */, - 4897326E2C8838615713B47EBE1FAD3D /* ImageProgressive.swift */, - 26E33A2601884DE73A47BD1A2690B14F /* ImageTransition.swift */, - 8A296F41C13699333333D3C630E92F80 /* ImageView+Kingfisher.swift */, - 02D1EC547B26205284A72A9F9473EB73 /* Indicator.swift */, - BE633CEBF91C6D351A438EB13D3F4E1F /* KF.swift */, - 5D15BEF86A67CAF30B602BB5A260F77B /* KFAnimatedImage.swift */, - F3EEB78584831D0B242BDCAA90CED58E /* KFImage.swift */, - 4CDB52D83D152A96E4E9CD244CA95DEA /* KFImageOptions.swift */, - 1F79F3D31F68D650253E6399BB7549D1 /* KFImageProtocol.swift */, - B43CFD3981B70FD7D609A5839B6A9218 /* KFImageRenderer.swift */, - 1A26B70DABA1CBACD8166FC00D07C081 /* KFOptionsSetter.swift */, - 83C7B6631D6A0479168A89D25B68A47C /* Kingfisher.swift */, - 864DAEC4F579E88712B7F5B447424FFC /* KingfisherError.swift */, - E8DAC31F0F6FE66D2C8AF5489F272CEA /* KingfisherManager.swift */, - A47365FDDB9CB0AC423AD6C3AF49AD21 /* KingfisherOptionsInfo.swift */, - 6BF75314341E15EAF5EC8A5095F2878D /* MemoryStorage.swift */, - 54B843EC8B993674D3288F87AEA9D122 /* NSButton+Kingfisher.swift */, - 6F69248BB24307C449A27A28477BAF56 /* NSTextAttachment+Kingfisher.swift */, - 774F6E017AC64D4F20E0DA360CA3FECF /* Placeholder.swift */, - 7A78C7902D8016F72AC215D2E28422F6 /* RedirectHandler.swift */, - 035EB38FC9B73D3AFE4C25920C1A1C32 /* RequestModifier.swift */, - 49B4649E1D11EB9D32BD3E212382EF1D /* Resource.swift */, - EF651BDF14D5A324318BC3F8C317F57A /* Result.swift */, - 3F8AC6801BE5A7B67B8032757817BE51 /* RetryStrategy.swift */, - E75F2A56E77879A823734237F7B0D90B /* Runtime.swift */, - 61585200C65A159F5B6731050D64BA59 /* SessionDataTask.swift */, - 9E1F7BD2E4AFF6989C1387F2558185ED /* SessionDelegate.swift */, - 84C0F797B07A4C95A5F90FB6C7E6A296 /* SizeExtensions.swift */, - 68A219042E44DB63D69EE3B79763130C /* Source.swift */, - 321C97AD92F4D32E4E33BB5B77FFE0FC /* Storage.swift */, - 7143F3A6986BF2AB2828E2DC37C9C09D /* String+MD5.swift */, - 67675B8E7AD45A266A61E66355512D15 /* TVMonogramView+Kingfisher.swift */, - D66CFC994F45F4A9A50C54C16724D763 /* UIButton+Kingfisher.swift */, - 2893522A05B1339D4388FD46FF52C52A /* WKInterfaceImage+Kingfisher.swift */, - 06AE3DF2432EBA8876A90CEDD3312552 /* Resources */, - DF485347F9CA2BC7B2DDEB48DB96D1CE /* Support Files */, - ); - name = Kingfisher; - path = Kingfisher; - sourceTree = ""; - }; - A2A70AADB5C5F2F057B310201B828389 /* SwiftDate */ = { - isa = PBXGroup; - children = ( - 475A4FB59CFB3BBA7FAC2F058F91F6E3 /* AssociatedValues.swift */, - D1968FDA9F67DC3333951BED4268CA52 /* Calendars.swift */, - 6E619DA6F3D601658833035BDA0E167C /* Commons.swift */, - A0422DB62B6B3C1A201A2762EBBB5A97 /* Date.swift */, - 8E6EB2C04F247D9F4D15B77F830EDF54 /* Date+Compare.swift */, - 14C0D566F3A17E2E2DBDE4098F711125 /* Date+Components.swift */, - 594D2794764B5796C8A422C4401A7E2F /* Date+Create.swift */, - DED1174C630E86B387EC34B8B1DD2DA3 /* Date+Math.swift */, - FF512DBF4BC4483F9F306E2B3A5CDB70 /* DateComponents+Extras.swift */, - 951E048B27AEAC006B779E585701262E /* DateInRegion.swift */, - 9B33B085086724D9914992FFDEF83E0B /* DateInRegion+Compare.swift */, - E48EF69AA55D5B91B47D8C1EB4BA4691 /* DateInRegion+Components.swift */, - C4C51E9357636F513F9828759A831069 /* DateInRegion+Create.swift */, - 4C554CE5C82E098DD3FF24BA75EE7490 /* DateInRegion+Math.swift */, - F0917B962F89415FDAAA876040A72E83 /* DateRepresentable.swift */, - 000257BCD6570C4B0371D660CCF8D3E1 /* DotNetParserFormatter.swift */, - 6EF651FECE01069367CDE0054F13249F /* Formatter+Protocols.swift */, - BD56DAD13E1185FD74DB7A8649E746CE /* Int+DateComponents.swift */, - F56A78070D28571910937BC0EE5894B8 /* ISOFormatter.swift */, - 86134D091916FFC476610F2EBFE771E2 /* ISOParser.swift */, - 2752DAD78FD744FDF40B54ADB0708E81 /* Locales.swift */, - FA38A01E090C908AE5886021034EE11D /* Region.swift */, - 728D0FC1713F8BD85AD5D78DF7CC3E06 /* RelativeFormatter.swift */, - 30D9FC3ABDA982F6CA82384628D6F165 /* RelativeFormatter+Style.swift */, - 418EE8CAF2C7190F191FC7554EAE20D3 /* RelativeFormatterLanguage.swift */, - 84DF245C6F43F9612AD56BC7D2ADB123 /* String+Parser.swift */, - 5B832310B3844FB85DE10E60B45F44A3 /* SwiftDate.swift */, - 2998944D54451B52A0996B2A9FDDD7DB /* TimeInterval+Formatter.swift */, - 7ECC357045F3ADF02DFCA0E0F6D4DEE0 /* TimePeriod.swift */, - 725764D6EDA104D9902913F6D02AB7FF /* TimePeriod+Support.swift */, - D19D1617C2FB2FC864606303AF174A6D /* TimePeriodChain.swift */, - 5AB27763B449AF051985734B3773B4ED /* TimePeriodCollection.swift */, - F44F0D93CFD9BB7C99C0FFCD686FF4E3 /* TimePeriodGroup.swift */, - 513DEC141FDDFD1CDAA8ED56A66AF623 /* TimePeriodProtocol.swift */, - C468581B4214846A9CA4FFED32F836D8 /* TimeStructures.swift */, - 55E9A15EC42E241A39E5CABDB5E0D428 /* Zones.swift */, - 47BE6238B9D2DAEAE874B27AAB7EA08A /* Resources */, - BE411CC01008BC5D5DCC757FE5D2C8D1 /* Support Files */, - ); - name = SwiftDate; - path = SwiftDate; - sourceTree = ""; - }; - A7A71010D8CCF5C1D9EF378910670798 /* Support Files */ = { - isa = PBXGroup; - children = ( - C521434F700F146BC7EEBBE2C6735ABF /* JXPagingView.modulemap */, - 04446B3B53853AB15AE3B7B85CF9343D /* JXPagingView-dummy.m */, - 2E89132366DFF0DC6A319EF946DF4CEA /* JXPagingView-Info.plist */, - E9C22A64A41E73EF4590DC6552E80DE8 /* JXPagingView-prefix.pch */, - 3EFEBDB3A2CB8EFD1E1BB0C882FC6500 /* JXPagingView-umbrella.h */, - 3EF5F786A9CD8F2E78E95733362FE187 /* JXPagingView.debug.xcconfig */, - D473476568C2D2300CCBAE8BAC883DCE /* JXPagingView.release.xcconfig */, - A690FBA7765087019C4F3AFEDD3E2CB0 /* ResourceBundle-JXPagingView-JXPagingView-Info.plist */, + 6A0E8D36BBC515C74EBC7794AC59F2B0 /* Alamofire.modulemap */, + 8DBA144AD6F6B2C8309C265DA2BDC2E4 /* Alamofire-dummy.m */, + B9C3E77992D38B50A812470C4F23CA7B /* Alamofire-Info.plist */, + 045B9D1FD204A0A244BBCA596A43DA72 /* Alamofire-prefix.pch */, + 5142C85EC3B111E46F791033767E12E2 /* Alamofire-umbrella.h */, + 02B941FC8A7C7118F2703A44433604B1 /* Alamofire.debug.xcconfig */, + F4020F6C1FB0BB6A58FC49D5FEFB7454 /* Alamofire.release.xcconfig */, + 8FDAFF7CF1DBCBEBF9C2AA409E378629 /* ResourceBundle-Alamofire-Alamofire-Info.plist */, ); name = "Support Files"; - path = "../Target Support Files/JXPagingView"; + path = "../Target Support Files/Alamofire"; sourceTree = ""; }; - B6A64081B24DF43D18A38FFA4FCFBDCA /* Resources */ = { + A5D0BD97B5C76AC4107C5DF3BFBB0294 /* Support Files */ = { isa = PBXGroup; children = ( - 84E085540B9A46FE188C3ED65522C329 /* PrivacyInfo.xcprivacy */, - ); - name = Resources; - sourceTree = ""; - }; - B6AE3338DA076FFE7E0B972538990B7A /* Pods */ = { - isa = PBXGroup; - children = ( - EF77375878C50B61FF2593542685106D /* Alamofire */, - 8B5308B450A10A8C99942BA8C8381883 /* IQKeyboardManagerSwift */, - 385385A8F793C6C811AF2F8236997A1B /* JXPagingView */, - DEA2AF0318429324985ACCA0C980A61F /* JXSegmentedView */, - A1102946118376B3CF7E47B1B547A7DC /* Kingfisher */, - 22B6388EE2F6A689255DDA896BFF58B8 /* MJRefresh */, - C822606ED58A2E9200F4156CDBDC3817 /* SnapKit */, - C5CF704E6D5B3EAE744A297EEFCB6EF0 /* SVProgressHUD */, - A2A70AADB5C5F2F057B310201B828389 /* SwiftDate */, - ); - name = Pods; - sourceTree = ""; - }; - BE411CC01008BC5D5DCC757FE5D2C8D1 /* Support Files */ = { - isa = PBXGroup; - children = ( - F4ED2B675426F9C1C129D85C5356428E /* SwiftDate.modulemap */, - 43FF45102B67E91CC3D79928C2717E40 /* SwiftDate-dummy.m */, - CA5885E0EE3717A1437053591B37EAD8 /* SwiftDate-Info.plist */, - C33A744358DA725C9E769351AB40C521 /* SwiftDate-prefix.pch */, - 207F4383A0CAE6E946319D62026A82F0 /* SwiftDate-umbrella.h */, - BE0D9928CC4ABC217232D13CC2A41C53 /* SwiftDate.debug.xcconfig */, - AA2205F6575D1182446627195A2B2D7D /* SwiftDate.release.xcconfig */, - ); - name = "Support Files"; - path = "../Target Support Files/SwiftDate"; - sourceTree = ""; - }; - C541269A8DAE75B69CA5E2E7AC45959D /* Support Files */ = { - isa = PBXGroup; - children = ( - 3050C5DBB1E4F1DF9FD1B33611FDBDAD /* SVProgressHUD.modulemap */, - 0EC583B42A01E6257C74C1DCD53164A8 /* SVProgressHUD-dummy.m */, - D96D6359D0BE44C30CD6EE91D83FA37B /* SVProgressHUD-Info.plist */, - D44F4C1AC6C2A207BC560F616003B1B3 /* SVProgressHUD-prefix.pch */, - C68BBA48149EB58E146B969600FB99B8 /* SVProgressHUD-umbrella.h */, - B06108CF75D8C3024C894159C039AF1C /* SVProgressHUD.debug.xcconfig */, - 6B82834CA67E7FF1B8610BF181E51004 /* SVProgressHUD.release.xcconfig */, + 0B222DC6983E3B7562A0B9F8F7F69F30 /* SVProgressHUD.modulemap */, + 322678B5A6885D82212954266BD74117 /* SVProgressHUD-dummy.m */, + 45AD14BC628E6E28901E62CB0D86DFDA /* SVProgressHUD-Info.plist */, + 453C24702999872F6BF14260FBE3A719 /* SVProgressHUD-prefix.pch */, + 5FE20AE5C1EF7F685ADC6C4B3A255B06 /* SVProgressHUD-umbrella.h */, + FF0B549F09E6F39720F2622AA415F744 /* SVProgressHUD.debug.xcconfig */, + 428DE06C7C62F2DF10FDF5B1C9C09F90 /* SVProgressHUD.release.xcconfig */, ); name = "Support Files"; path = "../Target Support Files/SVProgressHUD"; sourceTree = ""; }; - C5CF704E6D5B3EAE744A297EEFCB6EF0 /* SVProgressHUD */ = { + A6F0EC5B07E72B07F0890D59B025AD53 /* Resources */ = { isa = PBXGroup; children = ( - CB46B97DF031B3540C8A92C49DECB7F1 /* Core */, - C541269A8DAE75B69CA5E2E7AC45959D /* Support Files */, + D7F6101931EF4D62C239843C57A2C791 /* langs */, ); - name = SVProgressHUD; - path = SVProgressHUD; + name = Resources; sourceTree = ""; }; - C822606ED58A2E9200F4156CDBDC3817 /* SnapKit */ = { + AE883D96472D5FA4514BFD4EEF630AB1 /* Support Files */ = { isa = PBXGroup; children = ( - A26DF092CAB68C5202C9271E71C8F750 /* Constraint.swift */, - FD0AE0ACE265AEC714180F643F48C621 /* ConstraintAttributes.swift */, - 0837DA8FB2DF8148CEE1EA57A72EE570 /* ConstraintConfig.swift */, - 2EB8A72B411C5E90471A44666337E7D2 /* ConstraintConstantTarget.swift */, - 4BE5E05285408F9AA5B005A82CEFDA99 /* ConstraintDescription.swift */, - 14052CB3D5057DCF4593BD1E77D90942 /* ConstraintDirectionalInsets.swift */, - 570B90AE83D4F878D222EF7D7B7A18F9 /* ConstraintDirectionalInsetTarget.swift */, - 3A92A995C2CF52A57109B1F08F9E6044 /* ConstraintDSL.swift */, - 7682AD6DBFD519C9BA5ECCAC483C0CF4 /* ConstraintInsets.swift */, - 234A9BD48AA1E6813E36334F22C346D4 /* ConstraintInsetTarget.swift */, - 58092F279DEDF69EA7576C313DC62F89 /* ConstraintItem.swift */, - 1384016DEBC0AD27C484B338B4FBEDB5 /* ConstraintLayoutGuide.swift */, - 6B4DCC2536CD7F8BD55F0BAA08F110C7 /* ConstraintLayoutGuide+Extensions.swift */, - 482206CB35A8E05C272FA37C0A8BF762 /* ConstraintLayoutGuideDSL.swift */, - 6AFFBD2618F9F069B2DFFC4829E58831 /* ConstraintLayoutSupport.swift */, - 6ADEBF08B3C1426137C7A2ED9E5E54D8 /* ConstraintLayoutSupportDSL.swift */, - 50DD91D5AAEBFBFB4776365998B843B7 /* ConstraintMaker.swift */, - F088EAD83DD6725075FBD8403594C429 /* ConstraintMakerEditable.swift */, - 7EC53E7C517308EABC44AEFB75F5C064 /* ConstraintMakerExtendable.swift */, - 5545219DAAC101FE50450F293367D8C9 /* ConstraintMakerFinalizable.swift */, - 24091B247B320B28B53B7DB2A0EB996B /* ConstraintMakerPrioritizable.swift */, - B86EE2D4250A56EE1BB39FFEB834371D /* ConstraintMakerRelatable.swift */, - 9ABF6319E31E3E401B63195B3374007D /* ConstraintMakerRelatable+Extensions.swift */, - 1294C0C419D6967DAAFC5C21609CAAAF /* ConstraintMultiplierTarget.swift */, - 65E17F9E27F1D47AFFD1AE176DFE1657 /* ConstraintOffsetTarget.swift */, - A78C6D5D45F26A283595741E6566040D /* ConstraintPriority.swift */, - 667B5BDDACE02078C87EFED6AAB723C2 /* ConstraintPriorityTarget.swift */, - C483D88FC9B266C71FB54AA488C58BBD /* ConstraintRelatableTarget.swift */, - 7477826A60436064D61A5EFA564B9D6F /* ConstraintRelation.swift */, - 7FBC7116157221928733CE2DB074EB86 /* ConstraintView.swift */, - E9C19F89F8FC7DDEB64DB778A90CC2BC /* ConstraintView+Extensions.swift */, - 9CC5E0E6473324DB3F517B54470CBE6C /* ConstraintViewDSL.swift */, - E88C5C3FE92321767662E35C421964B3 /* Debugging.swift */, - FEE5906F519651F94E70B28F42C4C70B /* LayoutConstraint.swift */, - F7B2E649574AA0FAECB00E9B883C49A6 /* LayoutConstraintItem.swift */, - 440D319A1267C894C2AFFDA3362EBE9E /* Typealiases.swift */, - C4A16ABD25AB8AAB13E0072CADE42E65 /* UILayoutSupport+Extensions.swift */, - 124E79470B5BB059B6D2B9C11028C2E3 /* Resources */, - 6A4EF31B924CC5861010E154A95CAD31 /* Support Files */, + 268DBFBEBF1CFFE3E4096853AE17D797 /* Reachability.modulemap */, + CEA9547D3BA26D287FEB73F3765D4196 /* Reachability-dummy.m */, + 877E272496B26E87676CC7A7C6D5E26B /* Reachability-Info.plist */, + 48CA236C8CAAD1ED04F663C2CD8B0BB4 /* Reachability-prefix.pch */, + A794BB26E2D7D9E1AEEB5DB848440C14 /* Reachability-umbrella.h */, + 8DAFB222F308B491B658DA188BE99D5E /* Reachability.debug.xcconfig */, + E3D57D0843F4B1BD3133341EF6C971DA /* Reachability.release.xcconfig */, + 4F46CEA011DB42369F3F660437B779AF /* ResourceBundle-Reachability_Privacy-Reachability-Info.plist */, ); - name = SnapKit; - path = SnapKit; + name = "Support Files"; + path = "../Target Support Files/Reachability"; sourceTree = ""; }; - CB46B97DF031B3540C8A92C49DECB7F1 /* Core */ = { + B3C6A8E2F5CBA9AF1EB3FE9361E410E6 /* Support Files */ = { isa = PBXGroup; children = ( - 282ACC9FB46C251345FC5EB3AEF1B9CE /* SVIndefiniteAnimatedView.h */, - 1DE3D079E059E37BB8233A28ECEF0FC7 /* SVIndefiniteAnimatedView.m */, - 1AAB28B6A1A5AA4CC4A0EEB258D9DC57 /* SVProgressAnimatedView.h */, - BD1BE557E2AE4A84F2515EB14ABAF97B /* SVProgressAnimatedView.m */, - 7C589304DE45B301793ACB48B3D74C25 /* SVProgressHUD.h */, - 9B189CE2110C0A21199EB8FB87FCEB6D /* SVProgressHUD.m */, - F6D3AF216BBC3443539D5BAD7BE5DEDF /* SVRadialGradientLayer.h */, - AE5B1F68CF04B5EBB405EA5A612234B6 /* SVRadialGradientLayer.m */, - CCEE1B7961A9E05DD52CB4CE96EF43CF /* Resources */, + 1A7C07ADE12872C5F8AFB53F698FB941 /* JXPagingView.modulemap */, + 13E6439289EA56716750EA2531061260 /* JXPagingView-dummy.m */, + 95F712398C1D7BD6424897C31E306761 /* JXPagingView-Info.plist */, + 3F4510AFAF76845CC75D6531D2688FC8 /* JXPagingView-prefix.pch */, + CF39A9D25C909159253007F3C2573326 /* JXPagingView-umbrella.h */, + 282346A50AF20F6B23E26CD6DBED315E /* JXPagingView.debug.xcconfig */, + C96629463BEDB7A830E9D20B58BF2F36 /* JXPagingView.release.xcconfig */, + ADE2F4AB9CA5430A26D688A9559F54B5 /* ResourceBundle-JXPagingView-JXPagingView-Info.plist */, ); - name = Core; + name = "Support Files"; + path = "../Target Support Files/JXPagingView"; sourceTree = ""; }; - CCEE1B7961A9E05DD52CB4CE96EF43CF /* Resources */ = { + B92C72C9383AC260B8D7244D3FE24F2E /* Resources */ = { isa = PBXGroup; children = ( - 2C9B6C74D874F93335EDA5FBAC20675C /* PrivacyInfo.xcprivacy */, - A206F578A0BA9FC1C6B8B6078EFF6B9A /* SVProgressHUD.bundle */, + 6BEB2A0F222387786538A58D8C49C93A /* PrivacyInfo.xcprivacy */, + ); + name = Resources; + sourceTree = ""; + }; + BDE9EEA46E74056D6EFF38D38F8E1D6D /* MJRefresh */ = { + isa = PBXGroup; + children = ( + 4201EF3E35074620F075BCE228F7AEE5 /* MJRefresh.h */, + 202A71735BE68C199BF37F40C6CE4F19 /* MJRefreshAutoFooter.h */, + 803E8B9AADE51E96A38692668BA81BEF /* MJRefreshAutoFooter.m */, + 24B649D2878AC1DBFB2C4C94AEDDDD85 /* MJRefreshAutoGifFooter.h */, + 2700CE2AA0D809F25EBC8B3B4F1A57BF /* MJRefreshAutoGifFooter.m */, + 8133961A1352A59C4E8DBE54C15E2498 /* MJRefreshAutoNormalFooter.h */, + C9F3AF20D10C3D67C0A9CBA28E59360D /* MJRefreshAutoNormalFooter.m */, + EC85421228E0616F4EDD11D8427A8E09 /* MJRefreshAutoStateFooter.h */, + E3907D2AE7D5DAB04782E32C936D100E /* MJRefreshAutoStateFooter.m */, + F8DE10CA8E086617945517225C8F1E89 /* MJRefreshBackFooter.h */, + 816DB2F04B71F5F810AE6A0B24C8CC71 /* MJRefreshBackFooter.m */, + 53FD870D570E7836D8D696AFA899C174 /* MJRefreshBackGifFooter.h */, + 3C805EF5F028E6D4F8C47BE994E7975B /* MJRefreshBackGifFooter.m */, + 0A21DE84B8BBD51EE7C86AF1310AB8DB /* MJRefreshBackNormalFooter.h */, + D5C7A07FB940535B0ACB2DA9751FB827 /* MJRefreshBackNormalFooter.m */, + F2D1BE90941DDDB05B7D26E18A88B5C8 /* MJRefreshBackStateFooter.h */, + 9BE8B8F2FF82C76A9EF65784FB0C5A6E /* MJRefreshBackStateFooter.m */, + 882F5DAB4B06E95F1A5C4237CE16D896 /* MJRefreshComponent.h */, + 81BAB32DB922253ED14C81368B9744D0 /* MJRefreshComponent.m */, + B95736ACF688CE97879CE512184557E1 /* MJRefreshConfig.h */, + EDD206CF9334FE1FE5774C8F2948BF37 /* MJRefreshConfig.m */, + 379586CA36717B53DDF60E7C554E84C3 /* MJRefreshConst.h */, + 84F9D005F2B85BE22CA185718A0535FD /* MJRefreshConst.m */, + 0DB1F6055B6597CA3AB467E151EFF785 /* MJRefreshFooter.h */, + 5095BA006903F202D549CE2B34B14AA2 /* MJRefreshFooter.m */, + EB6BC0B4BBD642B6ACD05F64DBC1914A /* MJRefreshGifHeader.h */, + 5D010AF7D78D6C152BA967EB3141BFF7 /* MJRefreshGifHeader.m */, + 382BC06430FA8EEAEE68E10A5501E0ED /* MJRefreshHeader.h */, + AD2627618EECADAAD4DB704429147B41 /* MJRefreshHeader.m */, + 7CB1AF83C65AAD9D529E0E732D2C71B8 /* MJRefreshNormalHeader.h */, + 1C04292B44287517AD7ABDAF1E52CD95 /* MJRefreshNormalHeader.m */, + C01605AC5DD0430BA05B21A288DA50C1 /* MJRefreshNormalTrailer.h */, + 4F09B7EC32CFE88B4A26B887D4166EAE /* MJRefreshNormalTrailer.m */, + 9218CCDDFFCB0E70020117C647E18758 /* MJRefreshStateHeader.h */, + 50C5A927AB7AF95FB88E19C10BB2C621 /* MJRefreshStateHeader.m */, + 98621F211CCD821D8BED35AE739E356F /* MJRefreshStateTrailer.h */, + 68F6612E77D997F33CF6B5B66DC48B2D /* MJRefreshStateTrailer.m */, + AF9FD0EA190BF89FE270D6BAE52B3067 /* MJRefreshTrailer.h */, + 97FF07A23DCF1ECAAE9FF2E505D9B5C9 /* MJRefreshTrailer.m */, + C01E50B447FBD15488751F2415EB6952 /* NSBundle+MJRefresh.h */, + B12082E67BEB2172AFEE6485633D64DF /* NSBundle+MJRefresh.m */, + 77EFAEF82C69A4F5F14C4CDB669C9D9C /* UICollectionViewLayout+MJRefresh.h */, + 886F4DF873F4332C1BE04E6A33C70192 /* UICollectionViewLayout+MJRefresh.m */, + F4B34772DCB703B04668370DA984408B /* UIScrollView+MJExtension.h */, + 5C9F75C6ADBAE1B1E97DFE22EBDDCDFD /* UIScrollView+MJExtension.m */, + 2952FA317462D1808CC73087B4EC0DA8 /* UIScrollView+MJRefresh.h */, + E79F8ECDA1859613039CD7132B7282E8 /* UIScrollView+MJRefresh.m */, + 78E348898C88C370F77ED61FD69CB79C /* UIView+MJExtension.h */, + 403E083CF449B4B0144BC769B97043DC /* UIView+MJExtension.m */, + 1DA4D3F249680F28ACC640A644C31F14 /* Resources */, + 38A071E0A1AE8529E61938044E0BDF36 /* Support Files */, + ); + name = MJRefresh; + path = MJRefresh; + sourceTree = ""; + }; + CBE00C6E7CE6EE8D06C3C40111815FC7 /* Support Files */ = { + isa = PBXGroup; + children = ( + D3FB29E068A7B8DC04B1F093AFD6EC83 /* Tiercel.modulemap */, + F5B640A3A5A56570BD6D1B5693A7C4D8 /* Tiercel-dummy.m */, + 522D18990BF9254D37574205E2FE8309 /* Tiercel-Info.plist */, + 1572E364BB1E0FA13BCA456AF38E8969 /* Tiercel-prefix.pch */, + EF4708D4F1BBD4A3AFC205C27DD2D187 /* Tiercel-umbrella.h */, + 58058F4BB7020039FDF85930D314381A /* Tiercel.debug.xcconfig */, + B022AD44B90697AF0A3D63247178D60D /* Tiercel.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/Tiercel"; + sourceTree = ""; + }; + CDA3E2AA607A88124CB459491AAC1308 /* Resources */ = { + isa = PBXGroup; + children = ( + DA2A9ABE6AB044A8A75E993DD7A7D429 /* PrivacyInfo.xcprivacy */, ); name = Resources; sourceTree = ""; @@ -1599,162 +1812,334 @@ isa = PBXGroup; children = ( 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - D68CA58901FBF589D75F5E40F1EAF5BA /* Frameworks */, - B6AE3338DA076FFE7E0B972538990B7A /* Pods */, - 19C29489FB998EB8E7E019A0A9BE7772 /* Products */, + 2FE445AD883B9ABD3E0241A05A5B79FD /* Frameworks */, + E3E6F267D2806E6B47B45480550CDB0A /* Pods */, + 4811988BB32FB0ED0491A0F2C6DBC90D /* Products */, 4AD22DFAF5934D8E9D83B59C461B799B /* Targets Support Files */, ); sourceTree = ""; }; - D68CA58901FBF589D75F5E40F1EAF5BA /* Frameworks */ = { + D4FE3EB2EF4187AD0C43A8B72670BF1E /* iOS */ = { isa = PBXGroup; children = ( - 6625D72D58B79130D815A3A6D7072BCE /* iOS */, + FCC96C4E503A8275C142ABAFD3CE0373 /* Accelerate.framework */, + 45FF34A749D6457211D8DAA92C371E13 /* AudioToolbox.framework */, + 6490D638BB8DD75279D3868652896488 /* AVFoundation.framework */, + 543DBF6743B34366D2E9B3D55471D3AF /* CFNetwork.framework */, + AE492A9624C33092DFCBC183ECADE79D /* CoreGraphics.framework */, + 377E9052AEB44A18B62BD0611F91E391 /* Foundation.framework */, + C1881FE3DF05060537FF888C35D42D74 /* MediaPlayer.framework */, + A1EF6CE4DCA3A7EB57EF30D95912E1CC /* QuartzCore.framework */, + 63D0F1F07C36562816CB8E6D41FBDB3E /* SystemConfiguration.framework */, + AB406B5AB28CD8F5747EBE9498A2F869 /* UIKit.framework */, ); - name = Frameworks; + name = iOS; sourceTree = ""; }; - DEA2AF0318429324985ACCA0C980A61F /* JXSegmentedView */ = { + D7DA0C51F455B24E2BF481A190747F51 /* JXSegmentedView */ = { isa = PBXGroup; children = ( - FDE736DCB96A4842E66D08021E34D36C /* JXSegmentedAnimator.swift */, - CA08845BC3434B44AF0A3C8AFC111D93 /* JXSegmentedBaseCell.swift */, - 460F21D69A6B1F9493AACA08A2F865B9 /* JXSegmentedBaseDataSource.swift */, - 51A207FA0764909F90D1FCCDE191F924 /* JXSegmentedBaseItemModel.swift */, - 4F1FB2699EA33ABD4EDB7F856D39296F /* JXSegmentedCollectionView.swift */, - 36E3BC01C35A4242976E6B95431C9F17 /* JXSegmentedComponetGradientView.swift */, - 2215909E83BE0517F84079C1593E4E85 /* JXSegmentedDotCell.swift */, - 344A8E805D9E365E5FDF94223860238E /* JXSegmentedDotDataSource.swift */, - 8CFAC3CD688A5BEB743550BA40C464FF /* JXSegmentedDotItemModel.swift */, - E31B1C576F8187B6B1A1F160B90D6081 /* JXSegmentedIndicatorBackgroundView.swift */, - 0E81980AF59D081E0E8A9761D1C422BF /* JXSegmentedIndicatorBaseView.swift */, - B0CE74EB3DE207E3CAE98CDE17280DBA /* JXSegmentedIndicatorDotLineView.swift */, - 835F2888E352852912F4709F5E54512B /* JXSegmentedIndicatorDoubleLineView.swift */, - 6AC9CFB040D783BBBD3E787C142BFF2A /* JXSegmentedIndicatorGradientLineView.swift */, - 135384F59FC764376A1E1806D1B43835 /* JXSegmentedIndicatorGradientView.swift */, - 11E47356E556750015100B2119302E53 /* JXSegmentedIndicatorImageView.swift */, - F125D521A9DA47CB8D38C8C03AEF68A0 /* JXSegmentedIndicatorLineView.swift */, - FEC00130945EBD53372EFBC22FB8119E /* JXSegmentedIndicatorParams.swift */, - 89FE60E3816FD1A788423E90F6E28D8D /* JXSegmentedIndicatorProtocol.swift */, - 71DCFBED49883055AA94888206F221A9 /* JXSegmentedIndicatorRainbowLineView.swift */, - 09B2FF8925A3677E79E08183E04491D1 /* JXSegmentedIndicatorTriangleView.swift */, - BA90CEFF373663026D2F06371A3D4E9A /* JXSegmentedListContainerView.swift */, - AFFBC6D1E5E46588B6AAB4F0711B9D5E /* JXSegmentedNumberCell.swift */, - 1EC6A17326897FCD562597FF862EFB28 /* JXSegmentedNumberDataSource.swift */, - 0A704FDB2D535D699FD2D53CC6DFFC70 /* JXSegmentedNumberItemModel.swift */, - AB0205A7B7089CE3AA7AF45ACDE3629A /* JXSegmentedRTLLayout.swift */, - E95438C214C29A3893A1B32DE8B1898F /* JXSegmentedTitleAttributeCell.swift */, - 6B28820A2229C358F6F00F9A6EE1A659 /* JXSegmentedTitleAttributeDataSource.swift */, - 4FDEAE488C153F23CC530F9FC7878CE4 /* JXSegmentedTitleAttributeItemModel.swift */, - 6F86EAA1D247756258692097BEA95A21 /* JXSegmentedTitleCell.swift */, - E652FB17C57B0AF0273621B2B037A0C7 /* JXSegmentedTitleDataSource.swift */, - 225AEBA0EC32A3D5A52ECA46DBBED7F3 /* JXSegmentedTitleDynamicConfiguration.swift */, - 2820DA7141C1E36BCF6D4D5D96802EAF /* JXSegmentedTitleGradientCell.swift */, - 695AA5A7D6BF0B59DFBF71ABC3F3FECE /* JXSegmentedTitleGradientDataSource.swift */, - 2E39692FF9A73D5EC6D4AE2DE35E3C0C /* JXSegmentedTitleGradientItemModel.swift */, - 55914F40E7C5BD463245E2016A8906BC /* JXSegmentedTitleImageCell.swift */, - 4C1194040926B2C7D711B859F8AE4312 /* JXSegmentedTitleImageDataSource.swift */, - B65D6A9EE341D83823E03C122A72A5C1 /* JXSegmentedTitleImageItemModel.swift */, - DBF28FA874E18F2693E3B035356382F0 /* JXSegmentedTitleItemModel.swift */, - 0A8B5D1A6CBAD8FC5DA5D3D0DA7512BE /* JXSegmentedTitleOrImageCell.swift */, - AFCD84A7AB4756552C1E55E266165ACA /* JXSegmentedTitleOrImageDataSource.swift */, - FDD5BF7F25804ED6AC6B15D6801FE4AD /* JXSegmentedTitleOrImageItemModel.swift */, - 67318C8CDF97D3763D84C0B08910AD54 /* JXSegmentedView.swift */, - 7EB8A3AF649521A0ED645B8858FB4DDF /* JXSegmentedViewTool.swift */, - 3997AFF09C6B2741C9D946A854C69D26 /* Resources */, - 4ED80A127B880DDBD359D089C93079AC /* Support Files */, + 38D414568B081DC66E575280B0C9F52A /* JXSegmentedAnimator.swift */, + 192580DED59C4D3B135E94F830FCA5B3 /* JXSegmentedBaseCell.swift */, + A5CBEE13981AE8C02C5198ADE53051B6 /* JXSegmentedBaseDataSource.swift */, + E648B07B2374E25354F7F0A3630DB640 /* JXSegmentedBaseItemModel.swift */, + 07665A785548E8E674CB0127D9EED147 /* JXSegmentedCollectionView.swift */, + C6B4928DAC11CD0F28D07D85993E6E3B /* JXSegmentedComponetGradientView.swift */, + 4A7C6DA07E0232526BB718A5A28B5C3F /* JXSegmentedDotCell.swift */, + C9A720F6975F8168D2D97AFD10E7D7C3 /* JXSegmentedDotDataSource.swift */, + D0B8C3FB964677C8073E6FF156C5DBAE /* JXSegmentedDotItemModel.swift */, + 37EB4D3AA3806AA06D95FA6341AFE44A /* JXSegmentedIndicatorBackgroundView.swift */, + AA836459635F8DAA87C15B9C0EC55B49 /* JXSegmentedIndicatorBaseView.swift */, + 72BB35358219022632C6A9EF8A462D1F /* JXSegmentedIndicatorDotLineView.swift */, + C878F42BB922E2D8ABB27AB7491FFE39 /* JXSegmentedIndicatorDoubleLineView.swift */, + 2C74B3D86D767E3958819BDDB455791E /* JXSegmentedIndicatorGradientLineView.swift */, + DB06B0B50D662836095FC9FBDA523C32 /* JXSegmentedIndicatorGradientView.swift */, + EA6BEFA8047C293D703F2D4271C95AF4 /* JXSegmentedIndicatorImageView.swift */, + FCC89394EB57F40E775111D40AA7DB95 /* JXSegmentedIndicatorLineView.swift */, + 760BA463607D8ACAEEA23CB221E8880E /* JXSegmentedIndicatorParams.swift */, + D3A75B3E2D24B0E349B5094ABCB55CDC /* JXSegmentedIndicatorProtocol.swift */, + D0A946002663B961D8830937B5D6C506 /* JXSegmentedIndicatorRainbowLineView.swift */, + D61195B0A03C35A1089FEF5FA4E466DB /* JXSegmentedIndicatorTriangleView.swift */, + 7F813F795ABA741EA4048B038E7EC4C2 /* JXSegmentedListContainerView.swift */, + 5C43593ADD566299B8A9B4AC5DD8EBB0 /* JXSegmentedNumberCell.swift */, + 819F7D2E500376ED4EE8196C862FB5BE /* JXSegmentedNumberDataSource.swift */, + D0A63046EDC8D9C3318D3B5C0B3193AB /* JXSegmentedNumberItemModel.swift */, + 975850A5F9F51047F413323BDD9F682E /* JXSegmentedRTLLayout.swift */, + 740B2DF7CACF2EE62202C33B3853A83C /* JXSegmentedTitleAttributeCell.swift */, + 60F0C37A6C30DB306561499EA4E6BCDC /* JXSegmentedTitleAttributeDataSource.swift */, + D7B9BF1E5F3DBD4B648391B867DC65F5 /* JXSegmentedTitleAttributeItemModel.swift */, + 15CA5A8DF5A4A18FF5C1D85FDD30CC6A /* JXSegmentedTitleCell.swift */, + 3DB06C2DEEDF5BE52555F1C882B3020E /* JXSegmentedTitleDataSource.swift */, + 505ACEC8786DA864009CAFAD8D7ACC74 /* JXSegmentedTitleDynamicConfiguration.swift */, + B504D736329CAD81C67C2A8FC1F582A7 /* JXSegmentedTitleGradientCell.swift */, + C9929DB3FD7ABBE4F7AD3D7BA8F4ED4D /* JXSegmentedTitleGradientDataSource.swift */, + D0D9D13FC61F185D3F12CC9AB311971C /* JXSegmentedTitleGradientItemModel.swift */, + 7E1B99970D923DF6228679CB6A465895 /* JXSegmentedTitleImageCell.swift */, + A3E2B8B92BC8BB5E3EEF4A78D2464797 /* JXSegmentedTitleImageDataSource.swift */, + D810D4BC558D69052E21351222B3AF83 /* JXSegmentedTitleImageItemModel.swift */, + B6E3C1F981C5074CEFE799E99CBAC5B3 /* JXSegmentedTitleItemModel.swift */, + C15D0636F86B144901B1E79D45A5EF72 /* JXSegmentedTitleOrImageCell.swift */, + 72A654D4A98DED2922D1F9F69A777034 /* JXSegmentedTitleOrImageDataSource.swift */, + 8ED521E4CBFEA0F5D9E01C3CCE5307D5 /* JXSegmentedTitleOrImageItemModel.swift */, + 3C5D894D9E7403FED9BC0BC7102A3429 /* JXSegmentedView.swift */, + 921FCA711D3B8BFABCEB7BFD07F64831 /* JXSegmentedViewTool.swift */, + EFDD66062F911292413DCE8B56F88002 /* Resources */, + 8ECBF590DFCCD8FBDB7B8CED6462242A /* Support Files */, ); name = JXSegmentedView; path = JXSegmentedView; sourceTree = ""; }; - DF485347F9CA2BC7B2DDEB48DB96D1CE /* Support Files */ = { + D7F882C8D194EAEF8E4226A5EF684777 /* Core */ = { isa = PBXGroup; children = ( - A7AF9A69BFF4975345B92D217643792D /* Kingfisher.modulemap */, - 33033614BAFA0628A44D6CE5D2EDBB09 /* Kingfisher-dummy.m */, - F62422400EE70C424F8B9090F58835FC /* Kingfisher-Info.plist */, - B66FA26ED5139D4710E18EA56AEC64BE /* Kingfisher-prefix.pch */, - 814205A97B8158339AF6DCF93609360F /* Kingfisher-umbrella.h */, - DF2C075BA691343B5759F4F639FA08A6 /* Kingfisher.debug.xcconfig */, - E18618FEAC0ECEE393834FA4EEDDD337 /* Kingfisher.release.xcconfig */, - F3AC7EE5F91C56B1C96124A3C58154A5 /* ResourceBundle-Kingfisher-Kingfisher-Info.plist */, + BAC58F8D0F7FE424A8AD14D75A30EED3 /* SVIndefiniteAnimatedView.h */, + 3F496457565E077ADD0E21CF6E432F08 /* SVIndefiniteAnimatedView.m */, + 4975EAB4702DC92577C2ADE5E71D1FBA /* SVProgressAnimatedView.h */, + EBB6A03D1FED0558B0BA55987FF67D67 /* SVProgressAnimatedView.m */, + BFF72AA8AFE7B31BDC964105FF4B8FF0 /* SVProgressHUD.h */, + 8A7AE61E79272DD6D46024C339DD62FE /* SVProgressHUD.m */, + 54B4FA920379EF013B60907CCBEA03F8 /* SVRadialGradientLayer.h */, + E5F270A035F8B56FC159D8CF94EAAEE3 /* SVRadialGradientLayer.m */, + A0947FFE3F76937AA502C16B337EAA0D /* Resources */, + ); + name = Core; + sourceTree = ""; + }; + D857EE8D3444898A1B112BA3089C7841 /* Support Files */ = { + isa = PBXGroup; + children = ( + 9B4B0F4ACB790F3A81542BAEB92997B8 /* Kingfisher.modulemap */, + 68C6AC662AF5F96BCF9E223D29845982 /* Kingfisher-dummy.m */, + C72132A3576811404B6A267878A318E7 /* Kingfisher-Info.plist */, + 78C8DF2C971F4CFCCC05A727B2DE3B61 /* Kingfisher-prefix.pch */, + 4BB2313FBFE241B878F16C54B58F931A /* Kingfisher-umbrella.h */, + CA8217C50A39041C540DA99EAAE34AFE /* Kingfisher.debug.xcconfig */, + C6909F184654EB793056705412217353 /* Kingfisher.release.xcconfig */, + DC06AEF1EA119A09D38780349CDA2167 /* ResourceBundle-Kingfisher-Kingfisher-Info.plist */, ); name = "Support Files"; path = "../Target Support Files/Kingfisher"; sourceTree = ""; }; - E322A4AAC43BD85F10A108F72EC08C4E /* Support Files */ = { + D9B3109BDABC9D62418791A1D536C1EB /* FreeStreamer */ = { isa = PBXGroup; children = ( - AE8FD0EA90E35589FF45377FFE6445D6 /* IQKeyboardManagerSwift.modulemap */, - E1176C58859CF5334183B7786DAC8E87 /* IQKeyboardManagerSwift-dummy.m */, - 2FBE5C3E144BF69BC982850AFCE42CDB /* IQKeyboardManagerSwift-Info.plist */, - 52C0154703620B58E000A12A7DD03B58 /* IQKeyboardManagerSwift-prefix.pch */, - 98D0F4FF0A3E2660BE566528FDF31D03 /* IQKeyboardManagerSwift-umbrella.h */, - A3841A0A7F5A5EABE2100147A429E0C6 /* IQKeyboardManagerSwift.debug.xcconfig */, - AB089815F261E66CDE44D5F4DE72EE32 /* IQKeyboardManagerSwift.release.xcconfig */, + 18406CC620E7BB6D62552425CDBB8830 /* audio_queue.cpp */, + 1CF9D0C18975B91BFAC8937997951692 /* audio_queue.h */, + 91375183D8F9D59BCF0B45DD272B7E1D /* audio_stream.cpp */, + 6852392DB22126B984A79E66779323DD /* audio_stream.h */, + ADE694B2F5E068593CAAF87E546F52F1 /* caching_stream.cpp */, + 065328F2F98184B72B3BDFD13B1DE42D /* caching_stream.h */, + 6473EC965BEEC93B5FEB22E0B16F7E19 /* file_output.cpp */, + E54FE0BF84BDF5E90E2CC8A3B504BE3D /* file_output.h */, + 9598A6AA81E42757DB35F6F39AF08B4D /* file_stream.cpp */, + B4E3529EFC2A1018DD6CBC38DC725AFF /* file_stream.h */, + 2F04E5AE92F6C4FC3ECD3B58E312ED72 /* FSAudioController.h */, + 84BD083CF0E74D69596F699E0B0DAC6E /* FSAudioController.m */, + 2516E789B118A50B5D0289D7464D36EA /* FSAudioStream.h */, + CA0AA2C2CEE56C08EFDF78A739C0067B /* FSAudioStream.mm */, + 90391DB72A0C3217ED3F84491E44D38F /* FSCheckContentTypeRequest.h */, + 1FCD33E634B250907AA661A3C5F2C19E /* FSCheckContentTypeRequest.m */, + 0226611A6E76C8969EAF0DF4668B5B8C /* FSParsePlaylistRequest.h */, + EC2449762D441F345F1C4B12A439D080 /* FSParsePlaylistRequest.m */, + E18B8DB3629DE5ABA672D2EA056DA4FD /* FSParseRssPodcastFeedRequest.h */, + C5BC05F5D88A6640656E78CCE8D5018D /* FSParseRssPodcastFeedRequest.m */, + 8200191799409C278154BB17FF45D90E /* FSPlaylistItem.h */, + 095D58DA4F832B26482730E74AA23D62 /* FSPlaylistItem.m */, + FEFADF03AF0101330230B2A77AA48B0B /* FSXMLHttpRequest.h */, + 154B43B57C8ADCBE6975B5E4CB8D8BE8 /* FSXMLHttpRequest.m */, + E3E54AA8ABB092B11F44C7EAFB4A20B2 /* http_stream.cpp */, + D95BC8DF257D6DF9215BC495770135ED /* http_stream.h */, + 5984A98BF9B928B0D0AB3A27CF8809CE /* id3_parser.cpp */, + D603B15A4E766FC333BB15832FCEFE56 /* id3_parser.h */, + 9E1879F0CCEF6A60749ADDF0C4C21F45 /* input_stream.cpp */, + 98F57077D04460183803BC6B5E9765D7 /* input_stream.h */, + 3C1FD60AF28AF4C21BAC2E0EF16B4A52 /* stream_configuration.cpp */, + 69A60B500F2A28EF683EAB65A6568396 /* stream_configuration.h */, + E2FF7148DAFB9160B8354AF08CD80AFD /* Support Files */, + ); + name = FreeStreamer; + path = FreeStreamer; + sourceTree = ""; + }; + DA1A6EF252F007B2A1090869942591BC /* SwiftDate */ = { + isa = PBXGroup; + children = ( + 84ABD14F50DD9BE7BAAB5492C0D117A8 /* AssociatedValues.swift */, + 15E6514D83861C620C7A013C94AAB9F4 /* Calendars.swift */, + 918BA156001C627978B12B0E996C4789 /* Commons.swift */, + 08285191D7204A02272F9EB631D02466 /* Date.swift */, + F6F1C5A009876A142269C61CC4EC719C /* Date+Compare.swift */, + FF2F8D181C6E23DFEAF1BC524C922FAB /* Date+Components.swift */, + B06F8788675D48A6E7D3FAEDFB453AAA /* Date+Create.swift */, + 274BEE86492F3C61DE6D50C0A34C9991 /* Date+Math.swift */, + E2D2C5AD1222F2083B9740B6B843B0E0 /* DateComponents+Extras.swift */, + 72FEC0D9F81CB5F7CE5FDE9B82B17585 /* DateInRegion.swift */, + 815F60113B1A64FC31D1FFBBCF1B1505 /* DateInRegion+Compare.swift */, + C39BDFBFDF3DC43026E7C749C9D7E00A /* DateInRegion+Components.swift */, + 9805317D96D305A7B55B34F9ED5D7FC5 /* DateInRegion+Create.swift */, + 2F6348E3B982F1C6A8183DE435D49C1E /* DateInRegion+Math.swift */, + 38A6048835ACC3364C18CAD1AF32230E /* DateRepresentable.swift */, + 881A67EADDDFC811B6A54CC57595D3F8 /* DotNetParserFormatter.swift */, + 306B8810C66C0963989D2FF077E79536 /* Formatter+Protocols.swift */, + 3DDA2472F9717357C6BB8F71997F4BB1 /* Int+DateComponents.swift */, + 82423E826579CCC1BF9A80112E14C669 /* ISOFormatter.swift */, + 923221B8AC80D87BFC467045B41C9B5C /* ISOParser.swift */, + E782ECBFE74BC5E7C7A69C7C3D33C4FD /* Locales.swift */, + 43CC9FECC112EC531F730A6997A7AA46 /* Region.swift */, + 67D4BE2E3AB761AADDBACFB7F2B3FC96 /* RelativeFormatter.swift */, + D34FBEA0F135C4262CAB70AF8ED88B0D /* RelativeFormatter+Style.swift */, + 0246CF6ED39085AD58A2A4C18931E134 /* RelativeFormatterLanguage.swift */, + 27D37A59036A1116558DA573382EA9F2 /* String+Parser.swift */, + 884C2A1738B60FA764F7F9E770737AF2 /* SwiftDate.swift */, + 05D488858AA61B9A41D705A0719AB788 /* TimeInterval+Formatter.swift */, + 4C9A86C2DA499569E7EB235FF55769B3 /* TimePeriod.swift */, + 7B4870DA3D6F556BFAD1D0FD607F3F53 /* TimePeriod+Support.swift */, + 762E686A23E3C05BFD37C98D4CBA9424 /* TimePeriodChain.swift */, + EA0A49AFC87CB13825318FB69E4F3964 /* TimePeriodCollection.swift */, + 3FB693B14BC2F5BB232B918BC82C24C8 /* TimePeriodGroup.swift */, + 453A74195009C301A029E95CC589FB2F /* TimePeriodProtocol.swift */, + 40AD76C863D0635308D9E71629E36156 /* TimeStructures.swift */, + AE514086343B4C0D395517B412878058 /* Zones.swift */, + A6F0EC5B07E72B07F0890D59B025AD53 /* Resources */, + 15CB84B6F11ADF8A5B340D9E18CBAD9F /* Support Files */, + ); + name = SwiftDate; + path = SwiftDate; + sourceTree = ""; + }; + DB43F865DB152A2A32BD41DC947B37B0 /* SnapKit */ = { + isa = PBXGroup; + children = ( + D8447CAFBD6F721656250A2409FCA240 /* Constraint.swift */, + 0DD4FB14C7316C4A190EC2E458275B19 /* ConstraintAttributes.swift */, + 54020DD80FFCE6C88B44AD8F555783A9 /* ConstraintConfig.swift */, + F78B502A2849402E0A52A4D175507A77 /* ConstraintConstantTarget.swift */, + F98723A48A6B9BA3B70185B530DBD6C9 /* ConstraintDescription.swift */, + 9CDB522A56DDB4AA1E1324E6A24C7710 /* ConstraintDirectionalInsets.swift */, + CD9608BE5DC94C79C3B80889048CC2C9 /* ConstraintDirectionalInsetTarget.swift */, + B90B184E14693E477A94423C02031AB5 /* ConstraintDSL.swift */, + B11D4A2DB281C9D6FC1C296D5E1AA3F9 /* ConstraintInsets.swift */, + D7C2DC64431AFFDEAB367A051F329841 /* ConstraintInsetTarget.swift */, + BEB7265F1F1E1E6447ED57D564548109 /* ConstraintItem.swift */, + 20C642D426A1D3F5335387C32BBF5A18 /* ConstraintLayoutGuide.swift */, + 56F310A7F250FAC36DFA8E59F573C957 /* ConstraintLayoutGuide+Extensions.swift */, + 51D0D0C744FF259ADDA859BD13CB68F5 /* ConstraintLayoutGuideDSL.swift */, + 38AA948D61BC9D3D35F9604E5A3BC412 /* ConstraintLayoutSupport.swift */, + B26FEF928162FDEE5CFCE3EF99770572 /* ConstraintLayoutSupportDSL.swift */, + 51197F733B8466F86DCAA59DB57532F1 /* ConstraintMaker.swift */, + 6AE6589CF9C3283A65A9CCFB4A863A16 /* ConstraintMakerEditable.swift */, + 839F15386652EA3A10F991169D2BDC8F /* ConstraintMakerExtendable.swift */, + CA4DBCFFFB504E9F7837D6E205CD534A /* ConstraintMakerFinalizable.swift */, + 7F54DC04D93E64B2082AAFB9A1F4F83F /* ConstraintMakerPrioritizable.swift */, + 2618F09609D8CBB18F768D2EE4A14162 /* ConstraintMakerRelatable.swift */, + 8FE48C9953074AD1749565386F58D202 /* ConstraintMakerRelatable+Extensions.swift */, + 50068CE99A1D5B57ADA44E975763524F /* ConstraintMultiplierTarget.swift */, + C87A37B7FF63CC268718D512871DDD82 /* ConstraintOffsetTarget.swift */, + 9B18DFF40BBC24DB3E0E6AFD51871F38 /* ConstraintPriority.swift */, + A9E038FB2AF6F7D78755CD706F903925 /* ConstraintPriorityTarget.swift */, + 52F8DFC026AC5D16DD4867C898032787 /* ConstraintRelatableTarget.swift */, + CAAE3ADACEB4C422DD913DE51816FA68 /* ConstraintRelation.swift */, + B57AAC6360170C5570DBED4913F8439E /* ConstraintView.swift */, + 13CD38CD80EF3B99DB3C7067477E8CB9 /* ConstraintView+Extensions.swift */, + 55BEC05992BEE08743A5630C5E36E343 /* ConstraintViewDSL.swift */, + 98D2AD7AD0CA81C7FE5D1D243FE4573E /* Debugging.swift */, + EC171B63D94CE51CC1D237A219839672 /* LayoutConstraint.swift */, + 55061A053D3068F4AD9F6B8884C86A1B /* LayoutConstraintItem.swift */, + 28964B0FC810363A9BFD622768788FC3 /* Typealiases.swift */, + 42E0088C30E4A370DC59A6329299BF5F /* UILayoutSupport+Extensions.swift */, + B92C72C9383AC260B8D7244D3FE24F2E /* Resources */, + 0C1564B7298817050E59A8DC22616ADA /* Support Files */, + ); + name = SnapKit; + path = SnapKit; + sourceTree = ""; + }; + E17B840E88B5D7AF9A459679B4025963 /* JXPagingView */ = { + isa = PBXGroup; + children = ( + 63CC861A64AA47422317803C69ACD3AE /* Paging */, + B3C6A8E2F5CBA9AF1EB3FE9361E410E6 /* Support Files */, + ); + name = JXPagingView; + path = JXPagingView; + sourceTree = ""; + }; + E2FF7148DAFB9160B8354AF08CD80AFD /* Support Files */ = { + isa = PBXGroup; + children = ( + 3DFD149EDF95FEEEF188C759F64C9387 /* FreeStreamer.modulemap */, + BDBEAB153F0F1BFE8EE317B7794B999E /* FreeStreamer-dummy.m */, + 48F66D551A5D9A040F61CB55CE4CBC72 /* FreeStreamer-Info.plist */, + D284C6B097BA5EF23058D298D08850A9 /* FreeStreamer-prefix.pch */, + AECF3DE66EAD86C69CFB89D6FDEE4268 /* FreeStreamer-umbrella.h */, + 6F15B46DEB48666B2352074A40A67706 /* FreeStreamer.debug.xcconfig */, + 4DBABAD38468BD2235D1345DAFA6B8FF /* FreeStreamer.release.xcconfig */, ); name = "Support Files"; - path = "../Target Support Files/IQKeyboardManagerSwift"; + path = "../Target Support Files/FreeStreamer"; sourceTree = ""; }; - EF77375878C50B61FF2593542685106D /* Alamofire */ = { + E3E6F267D2806E6B47B45480550CDB0A /* Pods */ = { isa = PBXGroup; children = ( - B98A705AFD36170CD85E492121E37403 /* AFError.swift */, - 5347F4D40BA296B41A792189CB1331E9 /* Alamofire.swift */, - 1A8F4312D81FC10BD8029BE851C76498 /* AlamofireExtended.swift */, - 7052242B5B59DCEC7125EE873BC0202A /* AuthenticationInterceptor.swift */, - 28BCEACC594F9EA2E10117D26B3DA759 /* CachedResponseHandler.swift */, - 05A41A0B8D148A4BA10DC25292FA63E6 /* Combine.swift */, - E9B7826E0EF5E37E1704D27E7C361C78 /* Concurrency.swift */, - E6E279BCC1C2C3A2FA64E3E7F3FD5335 /* DataRequest.swift */, - F8C8C3813F3BA6EEC658167AE65D9419 /* DataStreamRequest.swift */, - 11AF900AC1C5B4CD4047B709266568E5 /* DispatchQueue+Alamofire.swift */, - 4316C38FD6E88F2FFBBB3BC6DF2C26FF /* DownloadRequest.swift */, - 393CAB07C81E1F3ACA4DDB1CAB615E1B /* EventMonitor.swift */, - 5699EE5AE55BF8C2FE30604FAED64A7C /* HTTPHeaders.swift */, - 6D3E36D2866A136708CF5F602D268BD7 /* HTTPMethod.swift */, - CB03A8FE1983F5F495EC9ADC0D6E7E6B /* MultipartFormData.swift */, - C94A8D6D5B3A24A5E000AF4DBE9434CF /* MultipartUpload.swift */, - 88647C8377C8FB3AE854C55C7B81559E /* NetworkReachabilityManager.swift */, - BF6B66846BA638DCD6F5ABB338C4AA41 /* Notifications.swift */, - 0AB50AF41FD17FD4D634D6CB5152810D /* OperationQueue+Alamofire.swift */, - 9AC28BE75055A99E1BD5B4B8D07E3C1B /* ParameterEncoder.swift */, - 368AFBDCEBBA20270829ACA09283D127 /* ParameterEncoding.swift */, - 0DB30FE6997717CE3E7589C60A9C7A9E /* Protected.swift */, - 6855310DB35D41B1E466E5EFFAF771BE /* RedirectHandler.swift */, - EB8D6628308EE28230C57E4CE1063A31 /* Request.swift */, - 6E6DA59208C9C680C1A08AB6B751A05E /* RequestCompression.swift */, - D89E20D36B74F4A890688A090C311698 /* RequestInterceptor.swift */, - 7C1665911416D598AAD937FC0E0D42E4 /* RequestTaskMap.swift */, - 1383471ECB9F37892C9CC3EF67CA0375 /* Response.swift */, - D88F8FEC13A3A1DB04F7186FA15AE1CB /* ResponseSerialization.swift */, - CB0F440BD5C5987BBAD5992F1CC0CCAA /* Result+Alamofire.swift */, - B10D7C8D9A8EFE21347CF640AE5AFCD9 /* RetryPolicy.swift */, - BAAC3162B7531D025AB197602FE75F31 /* ServerTrustEvaluation.swift */, - 8A9A6B3FF123B6733156B839AB90A488 /* Session.swift */, - 65BAE3F45733F6EB3DAE2F48599D8CAB /* SessionDelegate.swift */, - E681F9A4BFBE0245493029CEB1B8A1C8 /* StringEncoding+Alamofire.swift */, - 8B4EE5D13ED112C5C84DB3A6ADE8E7E0 /* UploadRequest.swift */, - EAD5CEA5D178799C659489E705F0BDE8 /* URLConvertible+URLRequestConvertible.swift */, - 08E28C2D9A6137B156647AC16D95A15E /* URLEncodedFormEncoder.swift */, - 0EA5D2A634B9432EA862513BDE2A5ED4 /* URLRequest+Alamofire.swift */, - 12A6ED7989C6F4843507D223BD102CC5 /* URLSessionConfiguration+Alamofire.swift */, - CA42DDB21EAB3527641F275C3D22E4D0 /* Validation.swift */, - A793885CE536998701B9632A60B45781 /* WebSocketRequest.swift */, - 21A960E74EC481373E719394FDBE27EF /* Resources */, - 5B7171C88905EE86E6FB47E5A6F4D2D4 /* Support Files */, + 3A3B35A921C15279048CFBB3EBE33F29 /* Alamofire */, + D9B3109BDABC9D62418791A1D536C1EB /* FreeStreamer */, + 5B4B1252A49F7CFE90EC436D9D9DDD99 /* IQKeyboardManagerSwift */, + E17B840E88B5D7AF9A459679B4025963 /* JXPagingView */, + D7DA0C51F455B24E2BF481A190747F51 /* JXSegmentedView */, + 39E22E3FB9C9F7B02ADD34EA96F52434 /* Kingfisher */, + BDE9EEA46E74056D6EFF38D38F8E1D6D /* MJRefresh */, + EF3D92145C4FB2F411FAB59BD23EFCFB /* Reachability */, + DB43F865DB152A2A32BD41DC947B37B0 /* SnapKit */, + 1B5C49DB2CAB492BF1D30955C6716144 /* SVProgressHUD */, + DA1A6EF252F007B2A1090869942591BC /* SwiftDate */, + 417E92E0D92975DAAD689C9DAC2BF494 /* Tiercel */, ); - name = Alamofire; - path = Alamofire; + name = Pods; sourceTree = ""; }; - F56AEB416F118ECBB450F18C0A431806 /* Resources */ = { + E4863A643CB900EA579321DC79EE5D85 /* Resources */ = { isa = PBXGroup; children = ( - EFE78C092A2B391D6411B2D75E0897C5 /* PrivacyInfo.xcprivacy */, + E05B0E4ECA120B98E41CFF3E9BAF9A59 /* PrivacyInfo.xcprivacy */, + ); + name = Resources; + sourceTree = ""; + }; + EA53D690CC029F1D86A2C43EE3C7B218 /* Resources */ = { + isa = PBXGroup; + children = ( + 446631A716250A41D285C5A69C889E4A /* PrivacyInfo.xcprivacy */, + ); + name = Resources; + sourceTree = ""; + }; + EF3D92145C4FB2F411FAB59BD23EFCFB /* Reachability */ = { + isa = PBXGroup; + children = ( + 0BC635388215C143B7967569B49BCBA1 /* Reachability.h */, + E7535AABBE9FF3A343D71B790DF34BE4 /* Reachability.m */, + EA53D690CC029F1D86A2C43EE3C7B218 /* Resources */, + AE883D96472D5FA4514BFD4EEF630AB1 /* Support Files */, + ); + name = Reachability; + path = Reachability; + sourceTree = ""; + }; + EFDD66062F911292413DCE8B56F88002 /* Resources */ = { + isa = PBXGroup; + children = ( + 54BC69F7BD5CCB745F0FB103B3478E38 /* PrivacyInfo.xcprivacy */, + ); + name = Resources; + sourceTree = ""; + }; + FB350D3C43D0AC138C4F084F04558B91 /* Resources */ = { + isa = PBXGroup; + children = ( + 3FC20D0CF5D2FFA24D77CA142F23498C /* PrivacyInfo.xcprivacy */, ); name = Resources; sourceTree = ""; @@ -1762,6 +2147,46 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 13E008DBCAEF85921C1FFB6CD44120DA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4318466387894387E637747807B70757 /* Tiercel-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 340899826D9D82188262B024ACC777BC /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 465D662A98148B3FE2CDCD0562AA2575 /* Pods-MusicPlayer-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 373490EE300A94825A8E87560CCC129F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4796934AE90BD750DECD38AD4EDFBA5F /* audio_queue.h in Headers */, + 91E3A6B42A7C2B5B57A4C3AD62DECDD4 /* audio_stream.h in Headers */, + 639CD31D622CB8B75228209AF6ACDA4C /* caching_stream.h in Headers */, + 4F16449E12C5246DC277899C7C5C2262 /* file_output.h in Headers */, + 978653D1A915C487464FCB9DB451C824 /* file_stream.h in Headers */, + C7484979F5A458C2BCBA24D30AB975F6 /* FreeStreamer-umbrella.h in Headers */, + 4BB69F4BB4D02CE3DEA258E2F86E46ED /* FSAudioController.h in Headers */, + 504BCCF03618854351F39813039AC5CA /* FSAudioStream.h in Headers */, + E4C41EFC9A8AADF64F5D08D670E2CDF4 /* FSCheckContentTypeRequest.h in Headers */, + EE9FB55C99BB7ACD0E67ACDA1573AFC7 /* FSParsePlaylistRequest.h in Headers */, + 261D92C1B19B41CB1899009080FEF6CD /* FSParseRssPodcastFeedRequest.h in Headers */, + 892E73423E4F812F4DBF43F2BEC21838 /* FSPlaylistItem.h in Headers */, + AEF9D1355E1DECBEF39B652B703FAC40 /* FSXMLHttpRequest.h in Headers */, + 3B2744E7B2A5C2AD33797BD2D280C8EF /* http_stream.h in Headers */, + 7EA7AFEFA0CE4029611DA026CDBE84B2 /* id3_parser.h in Headers */, + 1BDF02A8680914F05F4D8C2DDB44A9DE /* input_stream.h in Headers */, + 91DE350C08092882D3AD0384A4213398 /* stream_configuration.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3FBE446322EDFA526FB50BE550C80874 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -1819,11 +2244,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B454E5400DFCBAAE9FD01EC7644B8C7F /* Headers */ = { + 9E33E85104393636E8A1C0BFF507071B /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 5D4490556ADDD36F5100CF5D9F68ABF9 /* Pods-MusicPlayer-umbrella.h in Headers */, + C58DB71C6C298B2F2144AE20D9679995 /* Reachability.h in Headers */, + F17A4B446550E63A5D3E216E39918030 /* Reachability-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1886,7 +2312,7 @@ buildRules = ( ); dependencies = ( - 2C7D2C5A1F7AA8FA62801CB4684E0232 /* PBXTargetDependency */, + 6BAE6965BAB18903D8B844730A3D24E4 /* PBXTargetDependency */, ); name = SnapKit; productName = SnapKit; @@ -1913,11 +2339,11 @@ }; 52F43AC38D9FF80196C69FB03AEEFDDA /* JXSegmentedView-JXSegmentedView */ = { isa = PBXNativeTarget; - buildConfigurationList = 8192D6E7F891C6FB05E5D01514EABBE2 /* Build configuration list for PBXNativeTarget "JXSegmentedView-JXSegmentedView" */; + buildConfigurationList = 3632980DDBFB07AF9BA758C7D1701566 /* Build configuration list for PBXNativeTarget "JXSegmentedView-JXSegmentedView" */; buildPhases = ( - 6B53EDF320432C6FB2F2576B2E116CC6 /* Sources */, - E74997FAADCF49681E528FCB21A4D1AA /* Frameworks */, - 3AB3D3CDA12081B8903F0D5A9C1887FB /* Resources */, + E4160A826D6BA14ECEB7997AA112B0CA /* Sources */, + 10AD47F1D994001BE8E93F3858924E62 /* Frameworks */, + DAD6C9B389AF72E9492FC22B86C3E4F2 /* Resources */, ); buildRules = ( ); @@ -1958,7 +2384,7 @@ buildRules = ( ); dependencies = ( - FC0B50042A431604CEA4CD7FE470B9E2 /* PBXTargetDependency */, + C54C79467737191ED6E52ABA7774909B /* PBXTargetDependency */, ); name = MJRefresh; productName = MJRefresh; @@ -1967,25 +2393,28 @@ }; 686C644F1E3D20750B0EA86F7A8674B7 /* Pods-MusicPlayer */ = { isa = PBXNativeTarget; - buildConfigurationList = 38A9DB1BF7DB582FA154E6E85B017967 /* Build configuration list for PBXNativeTarget "Pods-MusicPlayer" */; + buildConfigurationList = 93389844FB1444F69DEFD95293885F5F /* Build configuration list for PBXNativeTarget "Pods-MusicPlayer" */; buildPhases = ( - B454E5400DFCBAAE9FD01EC7644B8C7F /* Headers */, - 3B6553FDFA7B234E555016891227351E /* Sources */, - 8CB5E1A5B008E0176BA7B33578DFF0C8 /* Frameworks */, - 621AAFBBAEDCEC01847BF59E99E1F36D /* Resources */, + 340899826D9D82188262B024ACC777BC /* Headers */, + E1DCFEB157773D76FF6B1CFCB0BB2D20 /* Sources */, + 0176F5222C51F523E3F8210910C4E1D2 /* Frameworks */, + D0A5C42D58A88097D9CC45B754C0E8E0 /* Resources */, ); buildRules = ( ); dependencies = ( - 9F42538473D8CD84E2B00227CCC5270F /* PBXTargetDependency */, - 9C4DCABEE4C8DE1C8C7F5F136424389A /* PBXTargetDependency */, - A419D3F35FFC28E3C110DC1E9BDD907A /* PBXTargetDependency */, - 4A583CB19C34725D587B75745C4FBB1F /* PBXTargetDependency */, - AF753EA8E0B40A33C32A44CAA21E6E1B /* PBXTargetDependency */, - 142AAB4FA977C078DA09CA68CD9F7AB0 /* PBXTargetDependency */, - 3AE06D852AB2AA0F21550B5A31B4B2A8 /* PBXTargetDependency */, - B5571C9BD8ACED22B2133870B863C3F5 /* PBXTargetDependency */, - B58C9ED1F196D76949A6ED62612F50C4 /* PBXTargetDependency */, + E2DC3F624B23D8E0750ACA4DE5B1BCD5 /* PBXTargetDependency */, + 023FCDC638FA96AA3161313ABC1C4B92 /* PBXTargetDependency */, + 664BD014FA95CA5DBB3358BE113C9761 /* PBXTargetDependency */, + 44B9940ECE98635E9225F41A834B6269 /* PBXTargetDependency */, + A283BFE3E022E92FA151ABA61608354F /* PBXTargetDependency */, + 40500836B333ACE66531454F50CE063A /* PBXTargetDependency */, + 46B2C1549FC4C94E91DB902195154DB1 /* PBXTargetDependency */, + CA6D726533C69FF06A93597B290C5A05 /* PBXTargetDependency */, + 549A26DE7BEF96ED9DB89772E41C2E47 /* PBXTargetDependency */, + E0D332B2854B1217A26FCDE686BFEB15 /* PBXTargetDependency */, + 57746CF8FAC3EB13145C7C21AD83AF6E /* PBXTargetDependency */, + A573195853A55A793F75BB544D951214 /* PBXTargetDependency */, ); name = "Pods-MusicPlayer"; productName = Pods_MusicPlayer; @@ -1994,11 +2423,11 @@ }; 8A8DB685241263AFDF5E6B20FE67B93A /* SnapKit-SnapKit_Privacy */ = { isa = PBXNativeTarget; - buildConfigurationList = 9B1967783C7584ADA7A063159ED92F0B /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */; + buildConfigurationList = 2291CF32BDEA7851DDFCA06373F249F1 /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */; buildPhases = ( - 975EC584D1C4839C3ED556A694D283BA /* Sources */, - 4B885211E9E65DAB21AAD17EBC56C6F1 /* Frameworks */, - 1FEFD1E5FA9185094B0AB02E6E69124A /* Resources */, + D9D8A1AC6382DDC04DD5DC81E949732E /* Sources */, + 1A0070C3094ABBC8B19356861CD6987D /* Frameworks */, + F808291409EE8D59094831656B890A7D /* Resources */, ); buildRules = ( ); @@ -2011,11 +2440,11 @@ }; 976126A1CE06DC6E162563800E1BDF14 /* Alamofire-Alamofire */ = { isa = PBXNativeTarget; - buildConfigurationList = 4ABFC85AD568923D7A67A236848CF419 /* Build configuration list for PBXNativeTarget "Alamofire-Alamofire" */; + buildConfigurationList = CEF8A4DD5960FE0C7DCB41D35871555F /* Build configuration list for PBXNativeTarget "Alamofire-Alamofire" */; buildPhases = ( - 986963C7968E6A40070C6340AF987110 /* Sources */, - E77A06A0307FF511269865599FB0CBC8 /* Frameworks */, - D52D9F52394C31B5EDC6D9A7475D483A /* Resources */, + D18062765E6EDF9A9EE2F252BF39C922 /* Sources */, + 9F5E232476A9F34AB6A6FE7583DD89E7 /* Frameworks */, + CA7834C64D8059FD6C2A1BD94FBDBFA3 /* Resources */, ); buildRules = ( ); @@ -2028,11 +2457,11 @@ }; 9828BBC09E9FB1238624113D7456E59E /* Kingfisher-Kingfisher */ = { isa = PBXNativeTarget; - buildConfigurationList = 387D22F9EF0A1068C65E4F1499A5D232 /* Build configuration list for PBXNativeTarget "Kingfisher-Kingfisher" */; + buildConfigurationList = FAC2315CCBF5446BEE4D12729EABA132 /* Build configuration list for PBXNativeTarget "Kingfisher-Kingfisher" */; buildPhases = ( - 341A979A7B6136565AD1967B52C3A2E9 /* Sources */, - ACBA24352126BCD74C702B7475807D88 /* Frameworks */, - 038AC83ADCB3D90F02617AEF82ED6BE7 /* Resources */, + B5F13496B7946A30D6EA4C6D31567995 /* Sources */, + 367C517E15190089AC7305B1526B6DF0 /* Frameworks */, + 0AF100714D17779BC27DF0C9000CBBD6 /* Resources */, ); buildRules = ( ); @@ -2045,11 +2474,11 @@ }; B26054DF1DEA11585A231AF6D1D80D5E /* MJRefresh-MJRefresh.Privacy */ = { isa = PBXNativeTarget; - buildConfigurationList = B84B7C31B677C791E25D754795F22921 /* Build configuration list for PBXNativeTarget "MJRefresh-MJRefresh.Privacy" */; + buildConfigurationList = ACB5D7B6A3B1693EEE5B05AE27AA2E3A /* Build configuration list for PBXNativeTarget "MJRefresh-MJRefresh.Privacy" */; buildPhases = ( - D73A00FA16CD75A875D6F6617EE144D2 /* Sources */, - 402E49A24B937179BD48FB96001CBF9D /* Frameworks */, - 3F7DFD623D64A5455DA0E9B055E44974 /* Resources */, + B26C8B04941F63EF1D8AA9EDBA9FA5D3 /* Sources */, + 6682ECE532E9F101074E2B2B437D7691 /* Frameworks */, + AE6CC37BFDDB084CB2F2119BC368E438 /* Resources */, ); buildRules = ( ); @@ -2062,11 +2491,11 @@ }; B2B2AD5303610D8EBEA025B2660C8EC5 /* JXPagingView-JXPagingView */ = { isa = PBXNativeTarget; - buildConfigurationList = F1FFFC5DAB1EC2D84580B2C5AD779F80 /* Build configuration list for PBXNativeTarget "JXPagingView-JXPagingView" */; + buildConfigurationList = 773DD54D18EEC3A44F2C380AA46CCF46 /* Build configuration list for PBXNativeTarget "JXPagingView-JXPagingView" */; buildPhases = ( - 4084A20021837E681EFB708E0F608493 /* Sources */, - 067799593B2F15037103C952D8E15BCA /* Frameworks */, - 35FF713FDC6FF4F02633772BC5868145 /* Resources */, + 82ED714EBA8C9B175CB11CCC6866AC3F /* Sources */, + DA261BE58D4355C7F4D7669AF3110431 /* Frameworks */, + F1400A34570AA7BD0F1535812C2BD28E /* Resources */, ); buildRules = ( ); @@ -2095,6 +2524,25 @@ productReference = A8E950A16D00F649C54FFB30F81D7842 /* IQKeyboardManagerSwift */; productType = "com.apple.product-type.framework"; }; + C3AAC0817EA4DC8BD9C0046F50078BF9 /* FreeStreamer */ = { + isa = PBXNativeTarget; + buildConfigurationList = 133F77FE3CFCFA878FBAFB5FACA68119 /* Build configuration list for PBXNativeTarget "FreeStreamer" */; + buildPhases = ( + 373490EE300A94825A8E87560CCC129F /* Headers */, + 62BAB9C130C81D302F14E49BBB84B10F /* Sources */, + 8FC306C8C914745906B745575E0B8896 /* Frameworks */, + AB1F16E41F9E7F720B0837C312948B7D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + BED2362AD4AABF08355558A3D7880E57 /* PBXTargetDependency */, + ); + name = FreeStreamer; + productName = FreeStreamer; + productReference = 359F20447DD6B2DABE3B77D75DA92F82 /* FreeStreamer */; + productType = "com.apple.product-type.framework"; + }; C4E1020AF425614337737213AA26DBD5 /* JXPagingView */ = { isa = PBXNativeTarget; buildConfigurationList = EF5C4DEBE2675B6E4A59F72D208FCC83 /* Build configuration list for PBXNativeTarget "JXPagingView" */; @@ -2107,13 +2555,49 @@ buildRules = ( ); dependencies = ( - 55177A922D890E642E071AD651EDEFF7 /* PBXTargetDependency */, + 356295DDBC51DF43A8978B2E8CE4428C /* PBXTargetDependency */, ); name = JXPagingView; productName = JXPagingView; productReference = 2F4A1CCB21DB7EA5A2ACEB11E374FBCA /* JXPagingView */; productType = "com.apple.product-type.framework"; }; + CAA047C0F5E4106F3904E8497FA17F97 /* Reachability */ = { + isa = PBXNativeTarget; + buildConfigurationList = F611DD208F4B8BE43C88B78FBEFF7356 /* Build configuration list for PBXNativeTarget "Reachability" */; + buildPhases = ( + 9E33E85104393636E8A1C0BFF507071B /* Headers */, + 2A07DF6D5B57E136BE970B16749E8FC6 /* Sources */, + FC6EA1D19142FAE0240219204EB1591D /* Frameworks */, + 1CE218FA19B5E4336F2C3D2247A5675C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 9FA458500BC21E9D7048AB634AB51DFE /* PBXTargetDependency */, + ); + name = Reachability; + productName = Reachability; + productReference = 400FF55D0451E7A8F33A3D0D3E11C1B9 /* Reachability */; + productType = "com.apple.product-type.framework"; + }; + D2787856C227A709315E3C9C4355A440 /* Reachability-Reachability_Privacy */ = { + isa = PBXNativeTarget; + buildConfigurationList = EC8B166C86423CA4DD32F097C7197EC2 /* Build configuration list for PBXNativeTarget "Reachability-Reachability_Privacy" */; + buildPhases = ( + B10638E4F522493554145E077A83B211 /* Sources */, + 447E1A1E9C78F7208DEC003469420ABA /* Frameworks */, + 4DC565A79DD3771E7A62D87CA8156AB4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Reachability-Reachability_Privacy"; + productName = Reachability_Privacy; + productReference = DFC89BE171DE7E648C53797695D8A220 /* Reachability-Reachability_Privacy */; + productType = "com.apple.product-type.bundle"; + }; E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */ = { isa = PBXNativeTarget; buildConfigurationList = 5208564FCF8321F270978B7DC8B0A7FB /* Build configuration list for PBXNativeTarget "Kingfisher" */; @@ -2126,7 +2610,7 @@ buildRules = ( ); dependencies = ( - E2C5174D78C8FF63A8DC3FA82C452A71 /* PBXTargetDependency */, + C12D087B60A66218DB10CE0C3025DE45 /* PBXTargetDependency */, ); name = Kingfisher; productName = Kingfisher; @@ -2145,7 +2629,7 @@ buildRules = ( ); dependencies = ( - 71CDB0472307C23BD3CD63A4784F44F7 /* PBXTargetDependency */, + AC2A63A99A8AD79C6E31DB92E3A5A1EC /* PBXTargetDependency */, ); name = JXSegmentedView; productName = JXSegmentedView; @@ -2164,13 +2648,31 @@ buildRules = ( ); dependencies = ( - A79D5863A3A25E1BE2AA0034A360CBB2 /* PBXTargetDependency */, + BAA5932CAC6035063BB75B4CE0D99F1B /* PBXTargetDependency */, ); name = Alamofire; productName = Alamofire; productReference = 5D797E9A5C5782CE845840781FA1CC81 /* Alamofire */; productType = "com.apple.product-type.framework"; }; + EF6413888FBA82A60EBB6F0A0EA14AD8 /* Tiercel */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9C5F169E34BEBB2A75B7CAAB8B10889E /* Build configuration list for PBXNativeTarget "Tiercel" */; + buildPhases = ( + 13E008DBCAEF85921C1FFB6CD44120DA /* Headers */, + 1184B56E93F7B9DAA2F09189E480356C /* Sources */, + 2824AFBC58BFB120AEB0EDF752307998 /* Frameworks */, + 7B21873D2196F035E3BE4560DD32BD3B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Tiercel; + productName = Tiercel; + productReference = DBD68AAF67BB25B9E1F44519178DAE0F /* Tiercel */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -2189,12 +2691,13 @@ en, ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; - productRefGroup = 19C29489FB998EB8E7E019A0A9BE7772 /* Products */; + productRefGroup = 4811988BB32FB0ED0491A0F2C6DBC90D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */, 976126A1CE06DC6E162563800E1BDF14 /* Alamofire-Alamofire */, + C3AAC0817EA4DC8BD9C0046F50078BF9 /* FreeStreamer */, B490E7485944099E16C9CBD79119D1D4 /* IQKeyboardManagerSwift */, C4E1020AF425614337737213AA26DBD5 /* JXPagingView */, B2B2AD5303610D8EBEA025B2660C8EC5 /* JXPagingView-JXPagingView */, @@ -2205,20 +2708,23 @@ 6868056D761E163D10FDAF8CF1C4D9B8 /* MJRefresh */, B26054DF1DEA11585A231AF6D1D80D5E /* MJRefresh-MJRefresh.Privacy */, 686C644F1E3D20750B0EA86F7A8674B7 /* Pods-MusicPlayer */, + CAA047C0F5E4106F3904E8497FA17F97 /* Reachability */, + D2787856C227A709315E3C9C4355A440 /* Reachability-Reachability_Privacy */, 19622742EBA51E823D6DAE3F8CDBFAD4 /* SnapKit */, 8A8DB685241263AFDF5E6B20FE67B93A /* SnapKit-SnapKit_Privacy */, 1C8D67D8B72D6BA42CCEDB648537A340 /* SVProgressHUD */, 6038CE6006EFBE9D905454CF01909C42 /* SwiftDate */, + EF6413888FBA82A60EBB6F0A0EA14AD8 /* Tiercel */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 038AC83ADCB3D90F02617AEF82ED6BE7 /* Resources */ = { + 0AF100714D17779BC27DF0C9000CBBD6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0FBCA5C01FA487754C6D0B1690FC9CDA /* PrivacyInfo.xcprivacy in Resources */, + C7A89997D6851D13CA43ECC1E7F60F90 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2238,35 +2744,11 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 1FEFD1E5FA9185094B0AB02E6E69124A /* Resources */ = { + 1CE218FA19B5E4336F2C3D2247A5675C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2C1014EEE6B2404C98A9F7AA534B534C /* PrivacyInfo.xcprivacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 35FF713FDC6FF4F02633772BC5868145 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AC46B7DF5115A19C887EDDC9C226B66E /* PrivacyInfo.xcprivacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3AB3D3CDA12081B8903F0D5A9C1887FB /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AD3CE903FA2BEE3EFA153E7FA7C9610E /* PrivacyInfo.xcprivacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3F7DFD623D64A5455DA0E9B055E44974 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 02298A4639A27A1808AA3188B7A566E9 /* PrivacyInfo.xcprivacy in Resources */, + 6CE562E7A66132DE1C4BCF574ECD3A26 /* Reachability-Reachability_Privacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2287,6 +2769,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4DC565A79DD3771E7A62D87CA8156AB4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EA774692DA04CE293FBB5AE6F2FC97CF /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5CEFF03EE9A7B5D7514E35F3AD2A314D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2295,13 +2785,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 621AAFBBAEDCEC01847BF59E99E1F36D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 7AB4C6E7F42074143BEB486E118CCE8A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2310,6 +2793,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7B21873D2196F035E3BE4560DD32BD3B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7D08E1D5F67A5709A06B4D00BE75A131 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2326,6 +2816,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AB1F16E41F9E7F720B0837C312948B7D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; AC47CEB36BE3DE7A7A5DB7547DA55F3D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -2335,17 +2832,88 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - D52D9F52394C31B5EDC6D9A7475D483A /* Resources */ = { + AE6CC37BFDDB084CB2F2119BC368E438 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 34DE07309F7BF7F614A480667602D079 /* PrivacyInfo.xcprivacy in Resources */, + E90B040D725864FEAF54241327117856 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CA7834C64D8059FD6C2A1BD94FBDBFA3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33882806FF45112A39D7F76DABA51A8B /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D0A5C42D58A88097D9CC45B754C0E8E0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DAD6C9B389AF72E9492FC22B86C3E4F2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AD4F228D11A3B45BE1024B59DEA210AA /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F1400A34570AA7BD0F1535812C2BD28E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 995C9FC5FD11DAE810CF708F3FAFB94A /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F808291409EE8D59094831656B890A7D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1159DF94A7740E8F0DC24481E1880378 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 1184B56E93F7B9DAA2F09189E480356C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 165B4E7F47A4A27EDF01B892B8E2A03B /* Array+Safe.swift in Sources */, + 5610B3D1D3CC41790D99E354C1201DA5 /* Cache.swift in Sources */, + E2BB8FD3C0A59A4D81911A2A70CC74C7 /* CodingUserInfoKey+Cache.swift in Sources */, + ED6D55B3F974F15896B01371C7FE20F3 /* Common.swift in Sources */, + 65EEB9B45B77C5733E6233759FE7BC76 /* Data+Hash.swift in Sources */, + CDD01A2CBF13747972C51B1AE7A10B31 /* DispatchQueue+Safe.swift in Sources */, + 81245F505360D8598E4A6C7D67E09F21 /* Double+TaskInfo.swift in Sources */, + 007E4A9363B819089774B481510E7DFC /* DownloadTask.swift in Sources */, + 43E7BD44853E62096917DAF77B6C1CE3 /* Executer.swift in Sources */, + F403D62A7C82B41016A4AADD34D08263 /* FileChecksumHelper.swift in Sources */, + 4EFCD7DECB92938B822DE9FB6CABD30F /* FileManager+AvailableCapacity.swift in Sources */, + F060261A5C0842947977A3CAAD96566B /* Int64+TaskInfo.swift in Sources */, + 371D974865C3554FFC4B2D487D12615E /* Notifications.swift in Sources */, + 49D5506D651C7F3E49D8DA20ABD46AF9 /* OperationQueue+DispatchQueue.swift in Sources */, + CFDC85864B426F19A908AD3E8F795D53 /* Protected.swift in Sources */, + FA50CBB47B030D9475376E2D6ED3FF04 /* ResumeDataHelper.swift in Sources */, + F6CF73614B01B233058CEDDB54309E60 /* SessionConfiguration.swift in Sources */, + E8A077D1FE8B40F1D67F5B3FB0613628 /* SessionDelegate.swift in Sources */, + 7C505E7C30A2E0FA68171866E82652AB /* SessionManager.swift in Sources */, + 9DE54A6FA3EA23631ADD6DEC4D190EC9 /* String+Hash.swift in Sources */, + 287D870088725A42B4DF1FFC8772EA19 /* Task.swift in Sources */, + B14FAF976D4BFA1065F209FC49DB722E /* Tiercel-dummy.m in Sources */, + 45ABFED1A9F6C0B330BB822AC08029A5 /* TiercelError.swift in Sources */, + 6E23DA3D58E8C10B007C321E13508FEF /* URLConvertible.swift in Sources */, + 661036CF70C0946F0ED7BAF395598868 /* URLSession+ResumeData.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 18CAFAC1E7DFA6B85E4A461A1071AE7C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2390,25 +2958,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 341A979A7B6136565AD1967B52C3A2E9 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 3B6553FDFA7B234E555016891227351E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 64B7C1BD8191B0963F3897F64977A7AA /* Pods-MusicPlayer-dummy.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4084A20021837E681EFB708E0F608493 /* Sources */ = { + 2A07DF6D5B57E136BE970B16749E8FC6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C439F42FE025FC9A6582455995D82001 /* Reachability.m in Sources */, + 938BE9A0AD26831EFD7B31DD37ADB045 /* Reachability-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2464,21 +3019,31 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6B53EDF320432C6FB2F2576B2E116CC6 /* Sources */ = { + 62BAB9C130C81D302F14E49BBB84B10F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2DDEFA6221FE75D88FE08E78DB380D16 /* audio_queue.cpp in Sources */, + B4BD80C0A29183CA9B1BDB5D8A73545C /* audio_stream.cpp in Sources */, + 202477A9BCD84C01BCBD8C02417ADA1E /* caching_stream.cpp in Sources */, + 917BE8C320E784763B6235AFF4751F0A /* file_output.cpp in Sources */, + 75B1BB0464F873B402FC5103A3AB691A /* file_stream.cpp in Sources */, + 64B00A7926F992ABED546BFCE00AA268 /* FreeStreamer-dummy.m in Sources */, + EB3408F453211B44E5F094E153EE0B6B /* FSAudioController.m in Sources */, + F1DA2E8CFCD0F77604F92816C5A373F1 /* FSAudioStream.mm in Sources */, + AD494A6CD3724BE63AA1BDDA1196742B /* FSCheckContentTypeRequest.m in Sources */, + AD6C25D10A1AA207FDB850E5F9A55758 /* FSParsePlaylistRequest.m in Sources */, + B375F549CB165674909966DC5BE9C1F8 /* FSParseRssPodcastFeedRequest.m in Sources */, + 1892ACC1F1247808BB4F54C8A11FA93B /* FSPlaylistItem.m in Sources */, + 71B478D19DAF06BE8A9C5D9DDEFC7342 /* FSXMLHttpRequest.m in Sources */, + 19E363D60D0FD878CFAAEF97EB99209B /* http_stream.cpp in Sources */, + 3EA3F347405C2C463842DC20333121E9 /* id3_parser.cpp in Sources */, + 6674DC681C85272A69C0E775CA7F102E /* input_stream.cpp in Sources */, + 1CA0950E4FCEAADB6C612F032B72BC3A /* stream_configuration.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 975EC584D1C4839C3ED556A694D283BA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 986963C7968E6A40070C6340AF987110 /* Sources */ = { + 82ED714EBA8C9B175CB11CCC6866AC3F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -2542,6 +3107,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B10638E4F522493554145E077A83B211 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B26C8B04941F63EF1D8AA9EDBA9FA5D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; B52EB97EAADC4B42F512DF593D183539 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2587,6 +3166,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B5F13496B7946A30D6EA4C6D31567995 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; B91DBF058753E0D63959220B34ECE3F9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2657,7 +3243,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - D73A00FA16CD75A875D6F6617EE144D2 /* Sources */ = { + D18062765E6EDF9A9EE2F252BF39C922 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D9D8A1AC6382DDC04DD5DC81E949732E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -2714,6 +3307,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E1DCFEB157773D76FF6B1CFCB0BB2D20 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2CA3365CAC025B79EF68EB13F395DD61 /* Pods-MusicPlayer-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E4160A826D6BA14ECEB7997AA112B0CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; F15C2AB492606C7255BF91F14E154EC6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2749,156 +3357,166 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 142AAB4FA977C078DA09CA68CD9F7AB0 /* PBXTargetDependency */ = { + 023FCDC638FA96AA3161313ABC1C4B92 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = MJRefresh; - target = 6868056D761E163D10FDAF8CF1C4D9B8 /* MJRefresh */; - targetProxy = 201623F455C6F19F61031B03545147D2 /* PBXContainerItemProxy */; + name = FreeStreamer; + target = C3AAC0817EA4DC8BD9C0046F50078BF9 /* FreeStreamer */; + targetProxy = 524E356992305B3EE8E57D80342EE4CE /* PBXContainerItemProxy */; }; - 2C7D2C5A1F7AA8FA62801CB4684E0232 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "SnapKit-SnapKit_Privacy"; - target = 8A8DB685241263AFDF5E6B20FE67B93A /* SnapKit-SnapKit_Privacy */; - targetProxy = 4679EE8967F1A89961ECB2793E44547B /* PBXContainerItemProxy */; - }; - 3AE06D852AB2AA0F21550B5A31B4B2A8 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SVProgressHUD; - target = 1C8D67D8B72D6BA42CCEDB648537A340 /* SVProgressHUD */; - targetProxy = 142BCA0E13C05E1A83FB4666F22A1552 /* PBXContainerItemProxy */; - }; - 4A583CB19C34725D587B75745C4FBB1F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = JXSegmentedView; - target = E863A9A96F52A35F47491E7B41ECEF9A /* JXSegmentedView */; - targetProxy = 2D57819E56EE60F3DF2C6E678E7D3569 /* PBXContainerItemProxy */; - }; - 55177A922D890E642E071AD651EDEFF7 /* PBXTargetDependency */ = { + 356295DDBC51DF43A8978B2E8CE4428C /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "JXPagingView-JXPagingView"; target = B2B2AD5303610D8EBEA025B2660C8EC5 /* JXPagingView-JXPagingView */; - targetProxy = 2C2690B3E48F2A7DAC7365AE33F00B17 /* PBXContainerItemProxy */; + targetProxy = 2E2B3CA35F01D0C8D4C8185315D025CD /* PBXContainerItemProxy */; }; - 71CDB0472307C23BD3CD63A4784F44F7 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "JXSegmentedView-JXSegmentedView"; - target = 52F43AC38D9FF80196C69FB03AEEFDDA /* JXSegmentedView-JXSegmentedView */; - targetProxy = 44996704F085A77FFFD3669AD2AD3058 /* PBXContainerItemProxy */; - }; - 9C4DCABEE4C8DE1C8C7F5F136424389A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = IQKeyboardManagerSwift; - target = B490E7485944099E16C9CBD79119D1D4 /* IQKeyboardManagerSwift */; - targetProxy = 2799C4D2D7768534EDE92F178DE24D47 /* PBXContainerItemProxy */; - }; - 9F42538473D8CD84E2B00227CCC5270F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = Alamofire; - target = EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */; - targetProxy = 30FFF29F3A02744304E52EE68F953BF3 /* PBXContainerItemProxy */; - }; - A419D3F35FFC28E3C110DC1E9BDD907A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = JXPagingView; - target = C4E1020AF425614337737213AA26DBD5 /* JXPagingView */; - targetProxy = 668AF990E71B0FD610A0100A739B125C /* PBXContainerItemProxy */; - }; - A79D5863A3A25E1BE2AA0034A360CBB2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "Alamofire-Alamofire"; - target = 976126A1CE06DC6E162563800E1BDF14 /* Alamofire-Alamofire */; - targetProxy = D80071531B55B03F9F25899249DEDFC3 /* PBXContainerItemProxy */; - }; - AF753EA8E0B40A33C32A44CAA21E6E1B /* PBXTargetDependency */ = { + 40500836B333ACE66531454F50CE063A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = Kingfisher; target = E8022D22FAA6690B5E1C379C1BCE1491 /* Kingfisher */; - targetProxy = E34EB63D4E4B105244F3DA6DA3F80E0D /* PBXContainerItemProxy */; + targetProxy = A7F47BB8EBC43499D97588770342BAAD /* PBXContainerItemProxy */; }; - B5571C9BD8ACED22B2133870B863C3F5 /* PBXTargetDependency */ = { + 44B9940ECE98635E9225F41A834B6269 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = SnapKit; - target = 19622742EBA51E823D6DAE3F8CDBFAD4 /* SnapKit */; - targetProxy = 7912D6332CD6248A05C5E894EA0DE591 /* PBXContainerItemProxy */; + name = JXPagingView; + target = C4E1020AF425614337737213AA26DBD5 /* JXPagingView */; + targetProxy = CCAF531EA632DE38D320090D7B70C51B /* PBXContainerItemProxy */; }; - B58C9ED1F196D76949A6ED62612F50C4 /* PBXTargetDependency */ = { + 46B2C1549FC4C94E91DB902195154DB1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = MJRefresh; + target = 6868056D761E163D10FDAF8CF1C4D9B8 /* MJRefresh */; + targetProxy = CC4C5AB9184F5773A95B9D8CE9592479 /* PBXContainerItemProxy */; + }; + 549A26DE7BEF96ED9DB89772E41C2E47 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SVProgressHUD; + target = 1C8D67D8B72D6BA42CCEDB648537A340 /* SVProgressHUD */; + targetProxy = 331B2125E99AE39DBDFFA681C20CC080 /* PBXContainerItemProxy */; + }; + 57746CF8FAC3EB13145C7C21AD83AF6E /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = SwiftDate; target = 6038CE6006EFBE9D905454CF01909C42 /* SwiftDate */; - targetProxy = A7BFC44123CBC428C300871C2A5C0B39 /* PBXContainerItemProxy */; + targetProxy = B9537C3853702B3E29EA534F3745DB82 /* PBXContainerItemProxy */; }; - E2C5174D78C8FF63A8DC3FA82C452A71 /* PBXTargetDependency */ = { + 664BD014FA95CA5DBB3358BE113C9761 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = IQKeyboardManagerSwift; + target = B490E7485944099E16C9CBD79119D1D4 /* IQKeyboardManagerSwift */; + targetProxy = 4381C03532A693F2DA89B10B86C9C6F4 /* PBXContainerItemProxy */; + }; + 6BAE6965BAB18903D8B844730A3D24E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "SnapKit-SnapKit_Privacy"; + target = 8A8DB685241263AFDF5E6B20FE67B93A /* SnapKit-SnapKit_Privacy */; + targetProxy = 39C9918B8C6E37752F9D7F1E93ADD657 /* PBXContainerItemProxy */; + }; + 9FA458500BC21E9D7048AB634AB51DFE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Reachability-Reachability_Privacy"; + target = D2787856C227A709315E3C9C4355A440 /* Reachability-Reachability_Privacy */; + targetProxy = 3F33C18D96BBF20741E70D698B6B4D06 /* PBXContainerItemProxy */; + }; + A283BFE3E022E92FA151ABA61608354F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = JXSegmentedView; + target = E863A9A96F52A35F47491E7B41ECEF9A /* JXSegmentedView */; + targetProxy = A706A072057FD19371ABE1ECCB4A823E /* PBXContainerItemProxy */; + }; + A573195853A55A793F75BB544D951214 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Tiercel; + target = EF6413888FBA82A60EBB6F0A0EA14AD8 /* Tiercel */; + targetProxy = 3A0CDAEC6F07673562A8BCC7E346368D /* PBXContainerItemProxy */; + }; + AC2A63A99A8AD79C6E31DB92E3A5A1EC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "JXSegmentedView-JXSegmentedView"; + target = 52F43AC38D9FF80196C69FB03AEEFDDA /* JXSegmentedView-JXSegmentedView */; + targetProxy = FE204BADD6B5C3BEB9549AB026F3C722 /* PBXContainerItemProxy */; + }; + BAA5932CAC6035063BB75B4CE0D99F1B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Alamofire-Alamofire"; + target = 976126A1CE06DC6E162563800E1BDF14 /* Alamofire-Alamofire */; + targetProxy = 4E9FA59D3929ED893F7ADADC913ED275 /* PBXContainerItemProxy */; + }; + BED2362AD4AABF08355558A3D7880E57 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Reachability; + target = CAA047C0F5E4106F3904E8497FA17F97 /* Reachability */; + targetProxy = 31DFA7A6B4A5F2D8D24A13CA986AA424 /* PBXContainerItemProxy */; + }; + C12D087B60A66218DB10CE0C3025DE45 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "Kingfisher-Kingfisher"; target = 9828BBC09E9FB1238624113D7456E59E /* Kingfisher-Kingfisher */; - targetProxy = 0A0E1A8C5689F0474D3549298F7E19CB /* PBXContainerItemProxy */; + targetProxy = F216D2B05F7D575335AC4A635F26284C /* PBXContainerItemProxy */; }; - FC0B50042A431604CEA4CD7FE470B9E2 /* PBXTargetDependency */ = { + C54C79467737191ED6E52ABA7774909B /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "MJRefresh-MJRefresh.Privacy"; target = B26054DF1DEA11585A231AF6D1D80D5E /* MJRefresh-MJRefresh.Privacy */; - targetProxy = 04009532C9602DFA4571AF336AFB7C5C /* PBXContainerItemProxy */; + targetProxy = DF0C9C1D510C4D5F716D7605A02114EE /* PBXContainerItemProxy */; + }; + CA6D726533C69FF06A93597B290C5A05 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Reachability; + target = CAA047C0F5E4106F3904E8497FA17F97 /* Reachability */; + targetProxy = 3FA2B62420DB5B3A067509B9CCF7DD2C /* PBXContainerItemProxy */; + }; + E0D332B2854B1217A26FCDE686BFEB15 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SnapKit; + target = 19622742EBA51E823D6DAE3F8CDBFAD4 /* SnapKit */; + targetProxy = 6A27223B7A93C4ADB9051FEFFAD57857 /* PBXContainerItemProxy */; + }; + E2DC3F624B23D8E0750ACA4DE5B1BCD5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Alamofire; + target = EAAA1AD3A8A1B59AB91319EE40752C6D /* Alamofire */; + targetProxy = 2B3DE7E3F6FF2DC64B43F8D54F5BA126 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 083EEC7FA1F0E9764FD5954A7D3E35AA /* Debug */ = { + 1176241D6B66146E9A3AD6AEB511048D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E623870FC6E557F7D90E41BF1892B184 /* Pods-MusicPlayer.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 14A34A929E243AD045487F198E80C530 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5EBCD15895605FBBBCD96B9C8E928D3B /* Alamofire.debug.xcconfig */; + baseConfigurationReference = E3D57D0843F4B1BD3133341EF6C971DA /* Reachability.release.xcconfig */; buildSettings = { CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Alamofire"; - IBSC_MODULE = Alamofire; - INFOPLIST_FILE = "Target Support Files/Alamofire/ResourceBundle-Alamofire-Alamofire-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - PRODUCT_NAME = Alamofire; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Reachability"; + IBSC_MODULE = Reachability; + INFOPLIST_FILE = "Target Support Files/Reachability/ResourceBundle-Reachability_Privacy-Reachability-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + PRODUCT_NAME = Reachability_Privacy; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = bundle; }; - name = Debug; + name = Release; + }; + 1AA213F67FA382CD936AA34B6E3023C1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CB76AA0035D5AC744AB7F388FA59EFAE /* JXSegmentedView.release.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXSegmentedView"; + IBSC_MODULE = JXSegmentedView; + INFOPLIST_FILE = "Target Support Files/JXSegmentedView/ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + PRODUCT_NAME = JXSegmentedView; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; }; 1AAF3B5EE94AF573BC39D3167C5DF211 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AA2205F6575D1182446627195A2B2D7D /* SwiftDate.release.xcconfig */; + baseConfigurationReference = 648A2050353718943995F249C37376B6 /* SwiftDate.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -2931,61 +3549,6 @@ }; name = Release; }; - 250A2303614C24E78E54410625196FE7 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F0F27AFF60F54ECC48396ECBB22D94EC /* Pods-MusicPlayer.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 266511746DBC33197FD9CF409AAE2718 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = D473476568C2D2300CCBAE8BAC883DCE /* JXPagingView.release.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXPagingView"; - IBSC_MODULE = JXPagingView; - INFOPLIST_FILE = "Target Support Files/JXPagingView/ResourceBundle-JXPagingView-JXPagingView-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - PRODUCT_NAME = JXPagingView; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Release; - }; 2B9E26EAE2CD392AD762421F663075A1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -3052,26 +3615,83 @@ }; name = Debug; }; - 2F988DCED078E24B8A93349A767C9757 /* Release */ = { + 310BA67B234771E353313CF2C8047D2D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E18618FEAC0ECEE393834FA4EEDDD337 /* Kingfisher.release.xcconfig */; + baseConfigurationReference = E3D57D0843F4B1BD3133341EF6C971DA /* Reachability.release.xcconfig */; buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Kingfisher"; - IBSC_MODULE = Kingfisher; - INFOPLIST_FILE = "Target Support Files/Kingfisher/ResourceBundle-Kingfisher-Kingfisher-Info.plist"; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Reachability/Reachability-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Reachability/Reachability-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - PRODUCT_NAME = Kingfisher; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Reachability/Reachability.modulemap"; + PRODUCT_MODULE_NAME = Reachability; + PRODUCT_NAME = Reachability; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.1; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 544C70A096A09B8CC12E7692B4B976F7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F0F27AFF60F54ECC48396ECBB22D94EC /* Pods-MusicPlayer.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Release; }; 563564487BD399E6F70C91C5FDCCBDA8 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6B82834CA67E7FF1B8610BF181E51004 /* SVProgressHUD.release.xcconfig */; + baseConfigurationReference = 428DE06C7C62F2DF10FDF5B1C9C09F90 /* SVProgressHUD.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3105,26 +3725,44 @@ }; name = Release; }; - 5C17DF439E18B7DF01FF5E6083AF4D67 /* Debug */ = { + 5E4C64D56B505D84A44C5EBF9879A28A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3AE4A701F7BAB983ED11B504E6487F76 /* MJRefresh.debug.xcconfig */; + baseConfigurationReference = 58058F4BB7020039FDF85930D314381A /* Tiercel.debug.xcconfig */; buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJRefresh"; - IBSC_MODULE = MJRefresh; - INFOPLIST_FILE = "Target Support Files/MJRefresh/ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - PRODUCT_NAME = MJRefresh.Privacy; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Tiercel/Tiercel-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Tiercel/Tiercel-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Tiercel/Tiercel.modulemap"; + PRODUCT_MODULE_NAME = Tiercel; + PRODUCT_NAME = Tiercel; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Debug; }; 5EA01C8F2E402725AC281C80AB12CDC0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DF2C075BA691343B5759F4F639FA08A6 /* Kingfisher.debug.xcconfig */; + baseConfigurationReference = CA8217C50A39041C540DA99EAAE34AFE /* Kingfisher.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3157,9 +3795,26 @@ }; name = Debug; }; - 6181874010C287EFEE382F18DBCA7E59 /* Release */ = { + 601D9ED7BB68637B44A02445FCC42191 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9DB02FC7AC88AE54321E64548CBFD1FF /* SnapKit.release.xcconfig */; + baseConfigurationReference = F4020F6C1FB0BB6A58FC49D5FEFB7454 /* Alamofire.release.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Alamofire"; + IBSC_MODULE = Alamofire; + INFOPLIST_FILE = "Target Support Files/Alamofire/ResourceBundle-Alamofire-Alamofire-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + 63D1D6D537B5261542F7B8802D40DD8A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 60917B37B15D85EBE318A3B375752857 /* SnapKit.debug.xcconfig */; buildSettings = { CODE_SIGNING_ALLOWED = NO; CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SnapKit"; @@ -3172,7 +3827,7 @@ TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = bundle; }; - name = Release; + name = Debug; }; 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */ = { isa = XCBuildConfiguration; @@ -3236,9 +3891,97 @@ }; name = Release; }; + 684E9AC69D182CBE83F0DA0A906FD2DA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E7EBBE4156E02FC8F034F0C0A10E31B0 /* SnapKit.release.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SnapKit"; + IBSC_MODULE = SnapKit; + INFOPLIST_FILE = "Target Support Files/SnapKit/ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + PRODUCT_NAME = SnapKit_Privacy; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + 68E631BDCA5E3EE9FAFC578D71CC3702 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 00A4DA108AE69BEB562DE490822C90D3 /* MJRefresh.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJRefresh"; + IBSC_MODULE = MJRefresh; + INFOPLIST_FILE = "Target Support Files/MJRefresh/ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + PRODUCT_NAME = MJRefresh.Privacy; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + 6C9600F75C3A4DCB2485B3B1D2BBFD85 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 18969DF6D4B5B1F3CFC962FAC18910BB /* JXSegmentedView.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXSegmentedView"; + IBSC_MODULE = JXSegmentedView; + INFOPLIST_FILE = "Target Support Files/JXSegmentedView/ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + PRODUCT_NAME = JXSegmentedView; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + 7142743E8C50E131E7C3A89F76B1197E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E623870FC6E557F7D90E41BF1892B184 /* Pods-MusicPlayer.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; 714A038955EDD712335B7293B4D7DAB3 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5EBCD15895605FBBBCD96B9C8E928D3B /* Alamofire.debug.xcconfig */; + baseConfigurationReference = 02B941FC8A7C7118F2703A44433604B1 /* Alamofire.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3273,7 +4016,7 @@ }; 796A20FEF97A102A877A06F7C64B8D4B /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0DB2B3B9A91393821FCD01D47CB39B40 /* JXSegmentedView.debug.xcconfig */; + baseConfigurationReference = 18969DF6D4B5B1F3CFC962FAC18910BB /* JXSegmentedView.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -3305,16 +4048,16 @@ }; name = Debug; }; - 91A90B0A1A76F626BFDB353BF2D519BA /* Release */ = { + 7F647797C18172295ACAFDD038A0F4BC /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7BD8D5C9444C4C58CF152CD4CC8DB0A3 /* Alamofire.release.xcconfig */; + baseConfigurationReference = C6909F184654EB793056705412217353 /* Kingfisher.release.xcconfig */; buildSettings = { CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Alamofire"; - IBSC_MODULE = Alamofire; - INFOPLIST_FILE = "Target Support Files/Alamofire/ResourceBundle-Alamofire-Alamofire-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - PRODUCT_NAME = Alamofire; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Kingfisher"; + IBSC_MODULE = Kingfisher; + INFOPLIST_FILE = "Target Support Files/Kingfisher/ResourceBundle-Kingfisher-Kingfisher-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + PRODUCT_NAME = Kingfisher; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3322,43 +4065,45 @@ }; name = Release; }; - 930F34F38147A861C25AB4A6F37A415B /* Debug */ = { + 8CACCE2CB5842FEF152EA06410C2C853 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0DB2B3B9A91393821FCD01D47CB39B40 /* JXSegmentedView.debug.xcconfig */; + baseConfigurationReference = B022AD44B90697AF0A3D63247178D60D /* Tiercel.release.xcconfig */; buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXSegmentedView"; - IBSC_MODULE = JXSegmentedView; - INFOPLIST_FILE = "Target Support Files/JXSegmentedView/ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - PRODUCT_NAME = JXSegmentedView; + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Tiercel/Tiercel-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Tiercel/Tiercel-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Tiercel/Tiercel.modulemap"; + PRODUCT_MODULE_NAME = Tiercel; + PRODUCT_NAME = Tiercel; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; - 965E63B0D5A8B44B1B484E0859EBAE62 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 451FEDBFD2A922230CA33D46534CD7F7 /* JXSegmentedView.release.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXSegmentedView"; - IBSC_MODULE = JXSegmentedView; - INFOPLIST_FILE = "Target Support Files/JXSegmentedView/ResourceBundle-JXSegmentedView-JXSegmentedView-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - PRODUCT_NAME = JXSegmentedView; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Release; }; 984240AE6A3D535525200348828211E3 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9DB02FC7AC88AE54321E64548CBFD1FF /* SnapKit.release.xcconfig */; + baseConfigurationReference = E7EBBE4156E02FC8F034F0C0A10E31B0 /* SnapKit.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3394,7 +4139,7 @@ }; 9C0B415A54C1C30F674208B8ADAB6C17 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BC90E0F634A75E57580CB83CB0B039E4 /* SnapKit.debug.xcconfig */; + baseConfigurationReference = 60917B37B15D85EBE318A3B375752857 /* SnapKit.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3429,7 +4174,7 @@ }; A6260E74821A6D83D56C6FD7B057B34D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A3841A0A7F5A5EABE2100147A429E0C6 /* IQKeyboardManagerSwift.debug.xcconfig */; + baseConfigurationReference = 7FA1D7762F6269DB38290C5D3BA1B21A /* IQKeyboardManagerSwift.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3464,7 +4209,7 @@ }; A7D05D839D2CE193DCEADAAF8897CD53 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 451FEDBFD2A922230CA33D46534CD7F7 /* JXSegmentedView.release.xcconfig */; + baseConfigurationReference = CB76AA0035D5AC744AB7F388FA59EFAE /* JXSegmentedView.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -3497,60 +4242,9 @@ }; name = Release; }; - B08D3C8F5A68D8459A91B94E9FBB57AB /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DF2C075BA691343B5759F4F639FA08A6 /* Kingfisher.debug.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Kingfisher"; - IBSC_MODULE = Kingfisher; - INFOPLIST_FILE = "Target Support Files/Kingfisher/ResourceBundle-Kingfisher-Kingfisher-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - PRODUCT_NAME = Kingfisher; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; - B22BD24E361F59ABEF30513214AB91C0 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3EF5F786A9CD8F2E78E95733362FE187 /* JXPagingView.debug.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXPagingView"; - IBSC_MODULE = JXPagingView; - INFOPLIST_FILE = "Target Support Files/JXPagingView/ResourceBundle-JXPagingView-JXPagingView-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - PRODUCT_NAME = JXPagingView; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; - B2B495D503BC782A9468D7FD0FB1800E /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = BC90E0F634A75E57580CB83CB0B039E4 /* SnapKit.debug.xcconfig */; - buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/SnapKit"; - IBSC_MODULE = SnapKit; - INFOPLIST_FILE = "Target Support Files/SnapKit/ResourceBundle-SnapKit_Privacy-SnapKit-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - PRODUCT_NAME = SnapKit_Privacy; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; - }; - name = Debug; - }; B31CF9907306152728F440F1F94804FD /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3AE4A701F7BAB983ED11B504E6487F76 /* MJRefresh.debug.xcconfig */; + baseConfigurationReference = 00A4DA108AE69BEB562DE490822C90D3 /* MJRefresh.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3583,9 +4277,60 @@ }; name = Debug; }; + B5B9FA367DBC42EDA1E7A257C0FB47BB /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CA8217C50A39041C540DA99EAAE34AFE /* Kingfisher.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Kingfisher"; + IBSC_MODULE = Kingfisher; + INFOPLIST_FILE = "Target Support Files/Kingfisher/ResourceBundle-Kingfisher-Kingfisher-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + PRODUCT_NAME = Kingfisher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + B5DCA8562673EEC44F39F12957553A9C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F2A555308B4D824394609780FA1D1CD6 /* MJRefresh.release.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJRefresh"; + IBSC_MODULE = MJRefresh; + INFOPLIST_FILE = "Target Support Files/MJRefresh/ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + PRODUCT_NAME = MJRefresh.Privacy; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + B908D4B877FFD8655475714CF8ABEF16 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 282346A50AF20F6B23E26CD6DBED315E /* JXPagingView.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXPagingView"; + IBSC_MODULE = JXPagingView; + INFOPLIST_FILE = "Target Support Files/JXPagingView/ResourceBundle-JXPagingView-JXPagingView-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + PRODUCT_NAME = JXPagingView; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; C107A00E8915CBCD07039B1CDC63628E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B06108CF75D8C3024C894159C039AF1C /* SVProgressHUD.debug.xcconfig */; + baseConfigurationReference = FF0B549F09E6F39720F2622AA415F744 /* SVProgressHUD.debug.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3620,7 +4365,7 @@ }; C37261D9FB6D4AFE04B143C9910CD592 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BE0D9928CC4ABC217232D13CC2A41C53 /* SwiftDate.debug.xcconfig */; + baseConfigurationReference = AFDB43B2AA4D143F40F21098E3079899 /* SwiftDate.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -3652,9 +4397,60 @@ }; name = Debug; }; + C493D916C6BFE2B344D96B8FC71F262C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C96629463BEDB7A830E9D20B58BF2F36 /* JXPagingView.release.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/JXPagingView"; + IBSC_MODULE = JXPagingView; + INFOPLIST_FILE = "Target Support Files/JXPagingView/ResourceBundle-JXPagingView-JXPagingView-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + PRODUCT_NAME = JXPagingView; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + C626EE8D2C698DF3B4204DB7D81E2C14 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 02B941FC8A7C7118F2703A44433604B1 /* Alamofire.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Alamofire"; + IBSC_MODULE = Alamofire; + INFOPLIST_FILE = "Target Support Files/Alamofire/ResourceBundle-Alamofire-Alamofire-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + PRODUCT_NAME = Alamofire; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + C864482C73ED6BE7CAEC8A7FD6DE10A0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8DAFB222F308B491B658DA188BE99D5E /* Reachability.debug.xcconfig */; + buildSettings = { + CODE_SIGNING_ALLOWED = NO; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Reachability"; + IBSC_MODULE = Reachability; + INFOPLIST_FILE = "Target Support Files/Reachability/ResourceBundle-Reachability_Privacy-Reachability-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + PRODUCT_NAME = Reachability_Privacy; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; CA60CF70D0AF64CB6C7F697460FBE2FE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7BD8D5C9444C4C58CF152CD4CC8DB0A3 /* Alamofire.release.xcconfig */; + baseConfigurationReference = F4020F6C1FB0BB6A58FC49D5FEFB7454 /* Alamofire.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3690,7 +4486,7 @@ }; D289924F49E290957622EAA7EBE53538 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E18618FEAC0ECEE393834FA4EEDDD337 /* Kingfisher.release.xcconfig */; + baseConfigurationReference = C6909F184654EB793056705412217353 /* Kingfisher.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3726,7 +4522,7 @@ }; D71AFD6535C82AF9CF91933A9687B877 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AC5E8113942F4B6D5C87580AA549D92C /* MJRefresh.release.xcconfig */; + baseConfigurationReference = F2A555308B4D824394609780FA1D1CD6 /* MJRefresh.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3762,7 +4558,7 @@ }; E0D85AC35AE1FCCF988F5CD7503DDB0F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D473476568C2D2300CCBAE8BAC883DCE /* JXPagingView.release.xcconfig */; + baseConfigurationReference = C96629463BEDB7A830E9D20B58BF2F36 /* JXPagingView.release.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -3795,9 +4591,44 @@ }; name = Release; }; + E5BDB76A4FC9FA1AB9D9999B9E02ABB8 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8DAFB222F308B491B658DA188BE99D5E /* Reachability.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/Reachability/Reachability-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/Reachability/Reachability-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/Reachability/Reachability.modulemap"; + PRODUCT_MODULE_NAME = Reachability; + PRODUCT_NAME = Reachability; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.1; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; F0C13041D6C39FC95F94A7215BB92DF1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3EF5F786A9CD8F2E78E95733362FE187 /* JXPagingView.debug.xcconfig */; + baseConfigurationReference = 282346A50AF20F6B23E26CD6DBED315E /* JXPagingView.debug.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -3831,7 +4662,7 @@ }; FB818BDDADBD0A197A07D52CF5BB68F5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AB089815F261E66CDE44D5F4DE72EE32 /* IQKeyboardManagerSwift.release.xcconfig */; + baseConfigurationReference = 66BC4906D86D7379576ACE241C5C5B97 /* IQKeyboardManagerSwift.release.xcconfig */; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; @@ -3865,40 +4696,101 @@ }; name = Release; }; - FE83682BF7CD2CFBBEFCEB21D24471E9 /* Release */ = { + FC6CC5D4E1CBEDDFB1AB651ABBA7F2A3 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AC5E8113942F4B6D5C87580AA549D92C /* MJRefresh.release.xcconfig */; + baseConfigurationReference = 4DBABAD38468BD2235D1345DAFA6B8FF /* FreeStreamer.release.xcconfig */; buildSettings = { - CODE_SIGNING_ALLOWED = NO; - CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJRefresh"; - IBSC_MODULE = MJRefresh; - INFOPLIST_FILE = "Target Support Files/MJRefresh/ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - PRODUCT_NAME = MJRefresh.Privacy; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/FreeStreamer/FreeStreamer-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/FreeStreamer/FreeStreamer-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/FreeStreamer/FreeStreamer.modulemap"; + PRODUCT_MODULE_NAME = FreeStreamer; + PRODUCT_NAME = FreeStreamer; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = bundle; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; name = Release; }; + FE4ABF0AB8B8B00448C72FBABA10D610 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6F15B46DEB48666B2352074A40A67706 /* FreeStreamer.debug.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/FreeStreamer/FreeStreamer-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/FreeStreamer/FreeStreamer-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/FreeStreamer/FreeStreamer.modulemap"; + PRODUCT_MODULE_NAME = FreeStreamer; + PRODUCT_NAME = FreeStreamer; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 387D22F9EF0A1068C65E4F1499A5D232 /* Build configuration list for PBXNativeTarget "Kingfisher-Kingfisher" */ = { + 133F77FE3CFCFA878FBAFB5FACA68119 /* Build configuration list for PBXNativeTarget "FreeStreamer" */ = { isa = XCConfigurationList; buildConfigurations = ( - B08D3C8F5A68D8459A91B94E9FBB57AB /* Debug */, - 2F988DCED078E24B8A93349A767C9757 /* Release */, + FE4ABF0AB8B8B00448C72FBABA10D610 /* Debug */, + FC6CC5D4E1CBEDDFB1AB651ABBA7F2A3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 38A9DB1BF7DB582FA154E6E85B017967 /* Build configuration list for PBXNativeTarget "Pods-MusicPlayer" */ = { + 2291CF32BDEA7851DDFCA06373F249F1 /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */ = { isa = XCConfigurationList; buildConfigurations = ( - 083EEC7FA1F0E9764FD5954A7D3E35AA /* Debug */, - 250A2303614C24E78E54410625196FE7 /* Release */, + 63D1D6D537B5261542F7B8802D40DD8A /* Debug */, + 684E9AC69D182CBE83F0DA0A906FD2DA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3632980DDBFB07AF9BA758C7D1701566 /* Build configuration list for PBXNativeTarget "JXSegmentedView-JXSegmentedView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6C9600F75C3A4DCB2485B3B1D2BBFD85 /* Debug */, + 1AA213F67FA382CD936AA34B6E3023C1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3921,15 +4813,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 4ABFC85AD568923D7A67A236848CF419 /* Build configuration list for PBXNativeTarget "Alamofire-Alamofire" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 14A34A929E243AD045487F198E80C530 /* Debug */, - 91A90B0A1A76F626BFDB353BF2D519BA /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 4C4F25F04C086237315FCAEF7661357E /* Build configuration list for PBXNativeTarget "SnapKit" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3957,11 +4840,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 8192D6E7F891C6FB05E5D01514EABBE2 /* Build configuration list for PBXNativeTarget "JXSegmentedView-JXSegmentedView" */ = { + 773DD54D18EEC3A44F2C380AA46CCF46 /* Build configuration list for PBXNativeTarget "JXPagingView-JXPagingView" */ = { isa = XCConfigurationList; buildConfigurations = ( - 930F34F38147A861C25AB4A6F37A415B /* Debug */, - 965E63B0D5A8B44B1B484E0859EBAE62 /* Release */, + B908D4B877FFD8655475714CF8ABEF16 /* Debug */, + C493D916C6BFE2B344D96B8FC71F262C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3975,20 +4858,38 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9B1967783C7584ADA7A063159ED92F0B /* Build configuration list for PBXNativeTarget "SnapKit-SnapKit_Privacy" */ = { + 93389844FB1444F69DEFD95293885F5F /* Build configuration list for PBXNativeTarget "Pods-MusicPlayer" */ = { isa = XCConfigurationList; buildConfigurations = ( - B2B495D503BC782A9468D7FD0FB1800E /* Debug */, - 6181874010C287EFEE382F18DBCA7E59 /* Release */, + 7142743E8C50E131E7C3A89F76B1197E /* Debug */, + 544C70A096A09B8CC12E7692B4B976F7 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B84B7C31B677C791E25D754795F22921 /* Build configuration list for PBXNativeTarget "MJRefresh-MJRefresh.Privacy" */ = { + 9C5F169E34BEBB2A75B7CAAB8B10889E /* Build configuration list for PBXNativeTarget "Tiercel" */ = { isa = XCConfigurationList; buildConfigurations = ( - 5C17DF439E18B7DF01FF5E6083AF4D67 /* Debug */, - FE83682BF7CD2CFBBEFCEB21D24471E9 /* Release */, + 5E4C64D56B505D84A44C5EBF9879A28A /* Debug */, + 8CACCE2CB5842FEF152EA06410C2C853 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ACB5D7B6A3B1693EEE5B05AE27AA2E3A /* Build configuration list for PBXNativeTarget "MJRefresh-MJRefresh.Privacy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 68E631BDCA5E3EE9FAFC578D71CC3702 /* Debug */, + B5DCA8562673EEC44F39F12957553A9C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CEF8A4DD5960FE0C7DCB41D35871555F /* Build configuration list for PBXNativeTarget "Alamofire-Alamofire" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C626EE8D2C698DF3B4204DB7D81E2C14 /* Debug */, + 601D9ED7BB68637B44A02445FCC42191 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -4002,6 +4903,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + EC8B166C86423CA4DD32F097C7197EC2 /* Build configuration list for PBXNativeTarget "Reachability-Reachability_Privacy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C864482C73ED6BE7CAEC8A7FD6DE10A0 /* Debug */, + 1176241D6B66146E9A3AD6AEB511048D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; EF5C4DEBE2675B6E4A59F72D208FCC83 /* Build configuration list for PBXNativeTarget "JXPagingView" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -4020,11 +4930,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F1FFFC5DAB1EC2D84580B2C5AD779F80 /* Build configuration list for PBXNativeTarget "JXPagingView-JXPagingView" */ = { + F611DD208F4B8BE43C88B78FBEFF7356 /* Build configuration list for PBXNativeTarget "Reachability" */ = { isa = XCConfigurationList; buildConfigurations = ( - B22BD24E361F59ABEF30513214AB91C0 /* Debug */, - 266511746DBC33197FD9CF409AAE2718 /* Release */, + E5BDB76A4FC9FA1AB9D9999B9E02ABB8 /* Debug */, + 310BA67B234771E353313CF2C8047D2D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -4038,6 +4948,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FAC2315CCBF5446BEE4D12729EABA132 /* Build configuration list for PBXNativeTarget "Kingfisher-Kingfisher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5B9FA367DBC42EDA1E7A257C0FB47BB /* Debug */, + 7F647797C18172295ACAFDD038A0F4BC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; diff --git a/Pods/Reachability/Framework/PrivacyInfo.xcprivacy b/Pods/Reachability/Framework/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..f8b2bfd --- /dev/null +++ b/Pods/Reachability/Framework/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + diff --git a/Pods/Reachability/LICENCE.txt b/Pods/Reachability/LICENCE.txt new file mode 100644 index 0000000..12b7844 --- /dev/null +++ b/Pods/Reachability/LICENCE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2011-2013, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/Pods/Reachability/README.md b/Pods/Reachability/README.md new file mode 100644 index 0000000..61c779c --- /dev/null +++ b/Pods/Reachability/README.md @@ -0,0 +1,159 @@ +[![Reference Status](https://www.versioneye.com/objective-c/reachability/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/reachability/references) +[![build-status](https://github.com/tonymillion/Reachability/actions/workflows/CI.yml/badge.svg)](https://github.com/tonymillion/Reachability/actions) + +# **WARNING** there have been reports of apps being rejected when Reachability is used in a framework. The only solution to this so far is to rename the class. + +# Reachability + +This is a drop-in replacement for Apple's `Reachability` class. It is ARC-compatible, and it uses the new GCD methods to notify of network interface changes. + +In addition to the standard `NSNotification`, it supports the use of blocks for when the network becomes reachable and unreachable. + +Finally, you can specify whether a WWAN connection is considered "reachable". + +*DO NOT OPEN BUGS UNTIL YOU HAVE TESTED ON DEVICE* + +**BEFORE YOU OPEN A BUG ABOUT iOS6/iOS5 build errors, use Tag 3.2 or 3.1 as they support assign types** + +## Requirements + +Once you have added the `.h/m` files to your project, simply: + +* Go to the `Project->TARGETS->Build Phases->Link Binary With Libraries`. +* Press the plus in the lower left of the list. +* Add `SystemConfiguration.framework`. + +Boom, you're done. + +## Examples + +### Block Example + +This sample uses blocks to notify when the interface state has changed. The blocks will be called on a **BACKGROUND THREAD**, so you need to dispatch UI updates onto the main thread. + +#### In Objective-C + +```objc +// Allocate a reachability object +Reachability* reach = [Reachability reachabilityWithHostname:@"www.google.com"]; + +// Set the blocks +reach.reachableBlock = ^(Reachability*reach) +{ + // keep in mind this is called on a background thread + // and if you are updating the UI it needs to happen + // on the main thread, like this: + + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"REACHABLE!"); + }); +}; + +reach.unreachableBlock = ^(Reachability*reach) +{ + NSLog(@"UNREACHABLE!"); +}; + +// Start the notifier, which will cause the reachability object to retain itself! +[reach startNotifier]; +``` + +### In Swift 3 + +```swift +import Reachability + +var reach: Reachability? + +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Allocate a reachability object + self.reach = Reachability.forInternetConnection() + + // Set the blocks + self.reach!.reachableBlock = { + (reach: Reachability?) -> Void in + + // keep in mind this is called on a background thread + // and if you are updating the UI it needs to happen + // on the main thread, like this: + DispatchQueue.main.async { + print("REACHABLE!") + } + } + + self.reach!.unreachableBlock = { + (reach: Reachability?) -> Void in + print("UNREACHABLE!") + } + + self.reach!.startNotifier() + + return true +} +``` + +### `NSNotification` Example + +This sample will use `NSNotification`s to notify when the interface has changed. They will be delivered on the **MAIN THREAD**, so you *can* do UI updates from within the function. + +In addition, it asks the `Reachability` object to consider the WWAN (3G/EDGE/CDMA) as a non-reachable connection (you might use this if you are writing a video streaming app, for example, to save the user's data plan). + +#### In Objective-C + +```objc +// Allocate a reachability object +Reachability* reach = [Reachability reachabilityWithHostname:@"www.google.com"]; + +// Tell the reachability that we DON'T want to be reachable on 3G/EDGE/CDMA +reach.reachableOnWWAN = NO; + +// Here we set up a NSNotification observer. The Reachability that caused the notification +// is passed in the object parameter +[[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reachabilityChanged:) + name:kReachabilityChangedNotification + object:nil]; + +[reach startNotifier]; +``` + +#### In Swift 3 + +```swift +import Reachability + +var reach: Reachability? + +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Allocate a reachability object + self.reach = Reachability.forInternetConnection() + + // Tell the reachability that we DON'T want to be reachable on 3G/EDGE/CDMA + self.reach!.reachableOnWWAN = false + + // Here we set up a NSNotification observer. The Reachability that caused the notification + // is passed in the object parameter + NotificationCenter.default.addObserver( + self, + selector: #selector(reachabilityChanged), + name: NSNotification.Name.reachabilityChanged, + object: nil + ) + + self.reach!.startNotifier() + + return true +} + +func reachabilityChanged(notification: NSNotification) { + if self.reach!.isReachableViaWiFi() || self.reach!.isReachableViaWWAN() { + print("Service available!!!") + } else { + print("No service available!!!") + } +} +``` + +## Tell the world + +Head over to [Projects using Reachability](https://github.com/tonymillion/Reachability/wiki/Projects-using-Reachability) and add your project for "Maximum Wins!". diff --git a/Pods/Reachability/Reachability.h b/Pods/Reachability/Reachability.h new file mode 100644 index 0000000..73883c3 --- /dev/null +++ b/Pods/Reachability/Reachability.h @@ -0,0 +1,103 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import + +//! Project version number for MacOSReachability. +FOUNDATION_EXPORT double ReachabilityVersionNumber; + +//! Project version string for MacOSReachability. +FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[]; + +/** + * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. + * + * @see http://nshipster.com/ns_enum-ns_options/ + **/ +#ifndef NS_ENUM +#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#endif + +extern NSString *const kReachabilityChangedNotification; + +typedef NS_ENUM(NSInteger, NetworkStatus) { + // Apple NetworkStatus Compatible Names. + NotReachable = 0, + ReachableViaWiFi = 2, + ReachableViaWWAN = 1 +}; + +@class Reachability; + +typedef void (^NetworkReachable)(Reachability * reachability); +typedef void (^NetworkUnreachable)(Reachability * reachability); +typedef void (^NetworkReachability)(Reachability * reachability, SCNetworkConnectionFlags flags); + + +@interface Reachability : NSObject + +@property (nonatomic, copy) NetworkReachable reachableBlock; +@property (nonatomic, copy) NetworkUnreachable unreachableBlock; +@property (nonatomic, copy) NetworkReachability reachabilityBlock; + +@property (nonatomic, assign) BOOL reachableOnWWAN; + + ++(instancetype)reachabilityWithHostname:(NSString*)hostname; +// This is identical to the function above, but is here to maintain +//compatibility with Apples original code. (see .m) ++(instancetype)reachabilityWithHostName:(NSString*)hostname; ++(instancetype)reachabilityForInternetConnection; ++(instancetype)reachabilityWithAddress:(void *)hostAddress; ++(instancetype)reachabilityForLocalWiFi; ++(instancetype)reachabilityWithURL:(NSURL*)url; + +-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; + +-(BOOL)startNotifier; +-(void)stopNotifier; + +-(BOOL)isReachable; +-(BOOL)isReachableViaWWAN; +-(BOOL)isReachableViaWiFi; + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired; // Identical DDG variant. +-(BOOL)connectionRequired; // Apple's routine. +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand; +// Is user intervention required? +-(BOOL)isInterventionRequired; + +-(NetworkStatus)currentReachabilityStatus; +-(SCNetworkReachabilityFlags)reachabilityFlags; +-(NSString*)currentReachabilityString; +-(NSString*)currentReachabilityFlags; + +@end diff --git a/Pods/Reachability/Reachability.m b/Pods/Reachability/Reachability.m new file mode 100644 index 0000000..5324390 --- /dev/null +++ b/Pods/Reachability/Reachability.m @@ -0,0 +1,508 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import "Reachability.h" + +#import +#import +#import +#import +#import +#import + + +NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification"; + + +@interface Reachability () + +@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; +@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; +@property (nonatomic, strong) id reachabilityObject; + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; + +@end + + +static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) +{ + return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", +#if TARGET_OS_IPHONE + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', +#else + 'X', +#endif + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +} + +// Start listening for reachability notifications on the current run loop +static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target) + + Reachability *reachability = ((__bridge Reachability*)info); + + // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, + // but what the heck eh? + @autoreleasepool + { + [reachability reachabilityChanged:flags]; + } +} + + +@implementation Reachability + +#pragma mark - Class Constructor Methods + ++(instancetype)reachabilityWithHostName:(NSString*)hostname +{ + return [Reachability reachabilityWithHostname:hostname]; +} + ++(instancetype)reachabilityWithHostname:(NSString*)hostname +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + + return reachability; + } + + return nil; +} + ++(instancetype)reachabilityWithAddress:(void *)hostAddress +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + + return reachability; + } + + return nil; +} + ++(instancetype)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress:&zeroAddress]; +} + ++(instancetype)reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + return [self reachabilityWithAddress:&localWifiAddress]; +} + ++(instancetype)reachabilityWithURL:(NSURL*)url +{ + id reachability; + + NSString *host = url.host; + BOOL isIpAddress = [self isIpAddress:host]; + + if (isIpAddress) + { + NSNumber *port = url.port ?: [url.scheme isEqualToString:@"https"] ? @(443) : @(80); + + struct sockaddr_in address; + address.sin_len = sizeof(address); + address.sin_family = AF_INET; + address.sin_port = htons([port intValue]); + address.sin_addr.s_addr = inet_addr([host UTF8String]); + + reachability = [self reachabilityWithAddress:&address]; + } + else + { + reachability = [self reachabilityWithHostname:host]; + } + + return reachability; +} + ++(BOOL)isIpAddress:(NSString*)host +{ + struct in_addr pin; + return 1 == inet_aton([host UTF8String], &pin); +} + + +// Initialization methods + +-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +{ + self = [super init]; + if (self != nil) + { + self.reachableOnWWAN = YES; + self.reachabilityRef = ref; + + // We need to create a serial queue. + // We allocate this once for the lifetime of the notifier. + + self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); + } + + return self; +} + +-(void)dealloc +{ + [self stopNotifier]; + + if(self.reachabilityRef) + { + CFRelease(self.reachabilityRef); + self.reachabilityRef = nil; + } + + self.reachableBlock = nil; + self.unreachableBlock = nil; + self.reachabilityBlock = nil; + self.reachabilitySerialQueue = nil; +} + +#pragma mark - Notifier Methods + +// Notifier +// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD +// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. +// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) + +-(BOOL)startNotifier +{ + // allow start notifier to be called multiple times + if(self.reachabilityObject && (self.reachabilityObject == self)) + { + return YES; + } + + + SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; + context.info = (__bridge void *)self; + + if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) + { + // Set it as our reachability queue, which will retain the queue + if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) + { + // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves + // woah + self.reachabilityObject = self; + return YES; + } + else + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); +#endif + + // UH OH - FAILURE - stop any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + } + } + else + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); +#endif + } + + // if we get here we fail at the internet + self.reachabilityObject = nil; + return NO; +} + +-(void)stopNotifier +{ + // First stop, any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // Unregister target from the GCD serial dispatch queue. + SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); + + self.reachabilityObject = nil; +} + +#pragma mark - reachability tests + +// This is for the case where you flick the airplane mode; +// you end up getting something like this: +//Reachability: WR ct----- +//Reachability: -- ------- +//Reachability: WR ct----- +//Reachability: -- ------- +// We treat this as 4 UNREACHABLE triggers - really apple should do better than this + +#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) + +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags +{ + BOOL connectionUP = YES; + + if(!(flags & kSCNetworkReachabilityFlagsReachable)) + connectionUP = NO; + + if( (flags & testcase) == testcase ) + connectionUP = NO; + +#if TARGET_OS_IPHONE + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + // We're on 3G. + if(!self.reachableOnWWAN) + { + // We don't want to connect when on 3G. + connectionUP = NO; + } + } +#endif + + return connectionUP; +} + +-(BOOL)isReachable +{ + SCNetworkReachabilityFlags flags; + + if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + return NO; + + return [self isReachableWithFlags:flags]; +} + +-(BOOL)isReachableViaWWAN +{ +#if TARGET_OS_IPHONE + + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + { + // Check we're REACHABLE + if(flags & kSCNetworkReachabilityFlagsReachable) + { + // Now, check we're on WWAN + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + return YES; + } + } + } +#endif + + return NO; +} + +-(BOOL)isReachableViaWiFi +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + { + // Check we're reachable + if((flags & kSCNetworkReachabilityFlagsReachable)) + { +#if TARGET_OS_IPHONE + // Check we're NOT on WWAN + if((flags & kSCNetworkReachabilityFlagsIsWWAN)) + { + return NO; + } +#endif + return YES; + } + } + + return NO; +} + + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired +{ + return [self connectionRequired]; +} + +-(BOOL)connectionRequired +{ + SCNetworkReachabilityFlags flags; + + if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); + } + + return NO; +} + +// Is user intervention required? +-(BOOL)isInterventionRequired +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kSCNetworkReachabilityFlagsInterventionRequired)); + } + + return NO; +} + + +#pragma mark - reachability status stuff + +-(NetworkStatus)currentReachabilityStatus +{ + if([self isReachable]) + { + if([self isReachableViaWiFi]) + return ReachableViaWiFi; + +#if TARGET_OS_IPHONE + return ReachableViaWWAN; +#endif + } + + return NotReachable; +} + +-(SCNetworkReachabilityFlags)reachabilityFlags +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + { + return flags; + } + + return 0; +} + +-(NSString*)currentReachabilityString +{ + NetworkStatus temp = [self currentReachabilityStatus]; + + if(temp == ReachableViaWWAN) + { + // Updated for the fact that we have CDMA phones now! + return NSLocalizedString(@"Cellular", @""); + } + if (temp == ReachableViaWiFi) + { + return NSLocalizedString(@"WiFi", @""); + } + + return NSLocalizedString(@"No Connection", @""); +} + +-(NSString*)currentReachabilityFlags +{ + return reachabilityFlags([self reachabilityFlags]); +} + +#pragma mark - Callback function calls this method + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags +{ + if([self isReachableWithFlags:flags]) + { + if(self.reachableBlock) + { + self.reachableBlock(self); + } + } + else + { + if(self.unreachableBlock) + { + self.unreachableBlock(self); + } + } + + if(self.reachabilityBlock) + { + self.reachabilityBlock(self, flags); + } + + // this makes sure the change notification happens on the MAIN THREAD + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification + object:self]; + }); +} + +#pragma mark - Debug Description + +- (NSString *) description +{ + NSString *description = [NSString stringWithFormat:@"<%@: %p (%@)>", + NSStringFromClass([self class]), self, [self currentReachabilityFlags]]; + return description; +} + +@end diff --git a/Pods/Target Support Files/FreeStreamer/FreeStreamer-Info.plist b/Pods/Target Support Files/FreeStreamer/FreeStreamer-Info.plist new file mode 100644 index 0000000..fee5e01 --- /dev/null +++ b/Pods/Target Support Files/FreeStreamer/FreeStreamer-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + ${PODS_DEVELOPMENT_LANGUAGE} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 4.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/FreeStreamer/FreeStreamer-dummy.m b/Pods/Target Support Files/FreeStreamer/FreeStreamer-dummy.m new file mode 100644 index 0000000..d9c42c4 --- /dev/null +++ b/Pods/Target Support Files/FreeStreamer/FreeStreamer-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_FreeStreamer : NSObject +@end +@implementation PodsDummy_FreeStreamer +@end diff --git a/Pods/Target Support Files/FreeStreamer/FreeStreamer-prefix.pch b/Pods/Target Support Files/FreeStreamer/FreeStreamer-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Pods/Target Support Files/FreeStreamer/FreeStreamer-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/FreeStreamer/FreeStreamer-umbrella.h b/Pods/Target Support Files/FreeStreamer/FreeStreamer-umbrella.h new file mode 100644 index 0000000..c56744e --- /dev/null +++ b/Pods/Target Support Files/FreeStreamer/FreeStreamer-umbrella.h @@ -0,0 +1,23 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "FSAudioController.h" +#import "FSAudioStream.h" +#import "FSCheckContentTypeRequest.h" +#import "FSParsePlaylistRequest.h" +#import "FSParseRssPodcastFeedRequest.h" +#import "FSPlaylistItem.h" +#import "FSXMLHttpRequest.h" + +FOUNDATION_EXPORT double FreeStreamerVersionNumber; +FOUNDATION_EXPORT const unsigned char FreeStreamerVersionString[]; + diff --git a/Pods/Target Support Files/FreeStreamer/FreeStreamer.debug.xcconfig b/Pods/Target Support Files/FreeStreamer/FreeStreamer.debug.xcconfig new file mode 100644 index 0000000..62fbecd --- /dev/null +++ b/Pods/Target Support Files/FreeStreamer/FreeStreamer.debug.xcconfig @@ -0,0 +1,15 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) $(SDKROOT)/usr/include/libxml2 +OTHER_LDFLAGS = $(inherited) -l"c++" -l"xml2" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "MediaPlayer" -framework "Reachability" -framework "SystemConfiguration" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/FreeStreamer +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/FreeStreamer/FreeStreamer.modulemap b/Pods/Target Support Files/FreeStreamer/FreeStreamer.modulemap new file mode 100644 index 0000000..c5e92a9 --- /dev/null +++ b/Pods/Target Support Files/FreeStreamer/FreeStreamer.modulemap @@ -0,0 +1,6 @@ +framework module FreeStreamer { + umbrella header "FreeStreamer-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/FreeStreamer/FreeStreamer.release.xcconfig b/Pods/Target Support Files/FreeStreamer/FreeStreamer.release.xcconfig new file mode 100644 index 0000000..62fbecd --- /dev/null +++ b/Pods/Target Support Files/FreeStreamer/FreeStreamer.release.xcconfig @@ -0,0 +1,15 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) $(SDKROOT)/usr/include/libxml2 +OTHER_LDFLAGS = $(inherited) -l"c++" -l"xml2" -framework "AVFoundation" -framework "AudioToolbox" -framework "CFNetwork" -framework "MediaPlayer" -framework "Reachability" -framework "SystemConfiguration" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/FreeStreamer +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.markdown b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.markdown index 3ce329d..ca9950a 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.markdown +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.markdown @@ -24,6 +24,63 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## FreeStreamer + +Copyright (c) 2011-2018 Matias Muhonen 穆马帝 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +The FreeStreamer framework bundles Reachability which is licensed under the following +license: + +Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + ## IQKeyboardManagerSwift MIT License @@ -148,6 +205,20 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## Reachability + +Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ## SVProgressHUD MIT License @@ -221,4 +292,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Tiercel + +Copyright (c) 2018 176516837@qq.com <176516837@qq.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.plist b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.plist index 9321de0..931aef1 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.plist +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-acknowledgements.plist @@ -41,6 +41,69 @@ THE SOFTWARE. Type PSGroupSpecifier + + FooterText + Copyright (c) 2011-2018 Matias Muhonen <mmu@iki.fi> 穆马帝 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +The FreeStreamer framework bundles Reachability which is licensed under the following +license: + +Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + License + BSD + Title + FreeStreamer + Type + PSGroupSpecifier + FooterText MIT License @@ -195,6 +258,26 @@ THE SOFTWARE. Type PSGroupSpecifier + + FooterText + Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + License + BSD + Title + Reachability + Type + PSGroupSpecifier + FooterText MIT License @@ -287,6 +370,35 @@ SOFTWARE. Type PSGroupSpecifier + + FooterText + Copyright (c) 2018 176516837@qq.com <176516837@qq.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + License + MIT + Title + Tiercel + Type + PSGroupSpecifier + FooterText Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-input-files.xcfilelist index b2fd609..5aadf5e 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-input-files.xcfilelist +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-input-files.xcfilelist @@ -1,10 +1,13 @@ ${PODS_ROOT}/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks.sh ${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework +${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework ${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework ${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework ${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework ${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework ${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework +${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework ${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework ${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework -${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework \ No newline at end of file +${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework +${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-output-files.xcfilelist index 94bc59d..5ada612 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-output-files.xcfilelist +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Debug-output-files.xcfilelist @@ -1,9 +1,12 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreeStreamer.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXPagingView.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXSegmentedView.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework -${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework \ No newline at end of file +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Tiercel.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-input-files.xcfilelist index b2fd609..5aadf5e 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-input-files.xcfilelist +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-input-files.xcfilelist @@ -1,10 +1,13 @@ ${PODS_ROOT}/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks.sh ${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework +${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework ${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework ${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework ${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework ${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework ${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework +${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework ${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework ${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework -${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework \ No newline at end of file +${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework +${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-output-files.xcfilelist index 94bc59d..5ada612 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-output-files.xcfilelist +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks-Release-output-files.xcfilelist @@ -1,9 +1,12 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreeStreamer.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXPagingView.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JXSegmentedView.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework -${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework \ No newline at end of file +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftDate.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Tiercel.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks.sh b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks.sh index cb695ba..6d4ae04 100755 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks.sh +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer-frameworks.sh @@ -177,25 +177,31 @@ code_sign_if_enabled() { if [[ "$CONFIGURATION" == "Debug" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" + install_framework "${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework" install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework" install_framework "${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework" install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework" install_framework "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework" install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework" + install_framework "${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework" install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework" install_framework "${BUILT_PRODUCTS_DIR}/JXPagingView/JXPagingView.framework" install_framework "${BUILT_PRODUCTS_DIR}/JXSegmentedView/JXSegmentedView.framework" install_framework "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework" install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework" install_framework "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework" install_framework "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/SwiftDate/SwiftDate.framework" + install_framework "${BUILT_PRODUCTS_DIR}/Tiercel/Tiercel.framework" fi if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then wait diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.debug.xcconfig b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.debug.xcconfig index 98ffa17..04029ee 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.debug.xcconfig +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.debug.xcconfig @@ -1,11 +1,11 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" $(SDKROOT)/usr/include/libxml2 LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift -OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -framework "Accelerate" -framework "Alamofire" -framework "CFNetwork" -framework "CoreGraphics" -framework "Foundation" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI" +OTHER_LDFLAGS = $(inherited) -l"c++" -l"swiftCoreGraphics" -l"xml2" -framework "AVFoundation" -framework "Accelerate" -framework "Alamofire" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "Foundation" -framework "FreeStreamer" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "MediaPlayer" -framework "QuartzCore" -framework "Reachability" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) diff --git a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.release.xcconfig b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.release.xcconfig index 98ffa17..04029ee 100644 --- a/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.release.xcconfig +++ b/Pods/Target Support Files/Pods-MusicPlayer/Pods-MusicPlayer.release.xcconfig @@ -1,11 +1,11 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXPagingView/JXPagingView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JXSegmentedView/JXSegmentedView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Kingfisher/Kingfisher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftDate/SwiftDate.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tiercel/Tiercel.framework/Headers" $(SDKROOT)/usr/include/libxml2 LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift -OTHER_LDFLAGS = $(inherited) -l"swiftCoreGraphics" -framework "Accelerate" -framework "Alamofire" -framework "CFNetwork" -framework "CoreGraphics" -framework "Foundation" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "QuartzCore" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI" +OTHER_LDFLAGS = $(inherited) -l"c++" -l"swiftCoreGraphics" -l"xml2" -framework "AVFoundation" -framework "Accelerate" -framework "Alamofire" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreGraphics" -framework "Foundation" -framework "FreeStreamer" -framework "IQKeyboardManagerSwift" -framework "JXPagingView" -framework "JXSegmentedView" -framework "Kingfisher" -framework "MJRefresh" -framework "MediaPlayer" -framework "QuartzCore" -framework "Reachability" -framework "SVProgressHUD" -framework "SnapKit" -framework "SwiftDate" -framework "SystemConfiguration" -framework "Tiercel" -framework "UIKit" -weak_framework "Combine" -weak_framework "SwiftUI" OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) diff --git a/Pods/Target Support Files/Reachability/Reachability-Info.plist b/Pods/Target Support Files/Reachability/Reachability-Info.plist new file mode 100644 index 0000000..3340c21 --- /dev/null +++ b/Pods/Target Support Files/Reachability/Reachability-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + ${PODS_DEVELOPMENT_LANGUAGE} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.7.6 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Reachability/Reachability-dummy.m b/Pods/Target Support Files/Reachability/Reachability-dummy.m new file mode 100644 index 0000000..119024a --- /dev/null +++ b/Pods/Target Support Files/Reachability/Reachability-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Reachability : NSObject +@end +@implementation PodsDummy_Reachability +@end diff --git a/Pods/Target Support Files/Reachability/Reachability-prefix.pch b/Pods/Target Support Files/Reachability/Reachability-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Pods/Target Support Files/Reachability/Reachability-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/Reachability/Reachability-umbrella.h b/Pods/Target Support Files/Reachability/Reachability-umbrella.h new file mode 100644 index 0000000..87c71b1 --- /dev/null +++ b/Pods/Target Support Files/Reachability/Reachability-umbrella.h @@ -0,0 +1,17 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "Reachability.h" + +FOUNDATION_EXPORT double ReachabilityVersionNumber; +FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[]; + diff --git a/Pods/Target Support Files/Reachability/Reachability.debug.xcconfig b/Pods/Target Support Files/Reachability/Reachability.debug.xcconfig new file mode 100644 index 0000000..e47cd0d --- /dev/null +++ b/Pods/Target Support Files/Reachability/Reachability.debug.xcconfig @@ -0,0 +1,13 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Reachability +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "SystemConfiguration" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Reachability +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Reachability/Reachability.modulemap b/Pods/Target Support Files/Reachability/Reachability.modulemap new file mode 100644 index 0000000..fe051ec --- /dev/null +++ b/Pods/Target Support Files/Reachability/Reachability.modulemap @@ -0,0 +1,6 @@ +framework module Reachability { + umbrella header "Reachability-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Reachability/Reachability.release.xcconfig b/Pods/Target Support Files/Reachability/Reachability.release.xcconfig new file mode 100644 index 0000000..e47cd0d --- /dev/null +++ b/Pods/Target Support Files/Reachability/Reachability.release.xcconfig @@ -0,0 +1,13 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Reachability +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "SystemConfiguration" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Reachability +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Reachability/ResourceBundle-Reachability_Privacy-Reachability-Info.plist b/Pods/Target Support Files/Reachability/ResourceBundle-Reachability_Privacy-Reachability-Info.plist new file mode 100644 index 0000000..e6d94b0 --- /dev/null +++ b/Pods/Target Support Files/Reachability/ResourceBundle-Reachability_Privacy-Reachability-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + ${PODS_DEVELOPMENT_LANGUAGE} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + BNDL + CFBundleShortVersionString + 3.7.6 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Tiercel/Tiercel-Info.plist b/Pods/Target Support Files/Tiercel/Tiercel-Info.plist new file mode 100644 index 0000000..c9a7e2c --- /dev/null +++ b/Pods/Target Support Files/Tiercel/Tiercel-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + ${PODS_DEVELOPMENT_LANGUAGE} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.2.5 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Tiercel/Tiercel-dummy.m b/Pods/Target Support Files/Tiercel/Tiercel-dummy.m new file mode 100644 index 0000000..932af14 --- /dev/null +++ b/Pods/Target Support Files/Tiercel/Tiercel-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Tiercel : NSObject +@end +@implementation PodsDummy_Tiercel +@end diff --git a/Pods/Target Support Files/Tiercel/Tiercel-prefix.pch b/Pods/Target Support Files/Tiercel/Tiercel-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Pods/Target Support Files/Tiercel/Tiercel-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/Tiercel/Tiercel-umbrella.h b/Pods/Target Support Files/Tiercel/Tiercel-umbrella.h new file mode 100644 index 0000000..4e718d2 --- /dev/null +++ b/Pods/Target Support Files/Tiercel/Tiercel-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double TiercelVersionNumber; +FOUNDATION_EXPORT const unsigned char TiercelVersionString[]; + diff --git a/Pods/Target Support Files/Tiercel/Tiercel.debug.xcconfig b/Pods/Target Support Files/Tiercel/Tiercel.debug.xcconfig new file mode 100644 index 0000000..ecdc664 --- /dev/null +++ b/Pods/Target Support Files/Tiercel/Tiercel.debug.xcconfig @@ -0,0 +1,14 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Tiercel +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Tiercel +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Tiercel/Tiercel.modulemap b/Pods/Target Support Files/Tiercel/Tiercel.modulemap new file mode 100644 index 0000000..5e8fce3 --- /dev/null +++ b/Pods/Target Support Files/Tiercel/Tiercel.modulemap @@ -0,0 +1,6 @@ +framework module Tiercel { + umbrella header "Tiercel-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Tiercel/Tiercel.release.xcconfig b/Pods/Target Support Files/Tiercel/Tiercel.release.xcconfig new file mode 100644 index 0000000..ecdc664 --- /dev/null +++ b/Pods/Target Support Files/Tiercel/Tiercel.release.xcconfig @@ -0,0 +1,14 @@ +CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Tiercel +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/Tiercel +PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Tiercel/LICENSE b/Pods/Tiercel/LICENSE new file mode 100644 index 0000000..5822810 --- /dev/null +++ b/Pods/Tiercel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 176516837@qq.com <176516837@qq.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Pods/Tiercel/README.md b/Pods/Tiercel/README.md new file mode 100644 index 0000000..cc7139e --- /dev/null +++ b/Pods/Tiercel/README.md @@ -0,0 +1,206 @@ +
+ +
+ +[![Version](https://img.shields.io/cocoapods/v/Tiercel.svg?style=flat)](http://cocoapods.org/pods/Tiercel) +[![Platform](https://img.shields.io/cocoapods/p/Tiercel.svg?style=flat)](http://cocoapods.org/pods/Tiercel) +[![Language](https://img.shields.io/badge/language-swift-red.svg?style=flat)]() +[![SPM](https://img.shields.io/badge/SPM-supported-DE5C43.svg?style=flat)](https://swift.org/package-manager/) +[![Support](https://img.shields.io/badge/support-iOS%2010%2B%20-brightgreen.svg?style=flat)](https://www.apple.com/nl/ios/) +[![License](https://img.shields.io/cocoapods/l/Tiercel.svg?style=flat)](http://cocoapods.org/pods/Tiercel) + +Tiercel 是一个简单易用、功能丰富的纯 Swift 下载框架,支持原生级别后台下载,拥有强大的任务管理功能,可以满足下载类 APP 的大部分需求。 + +如果你使用的开发语言是 Objective-C ,可以使用 [TiercelObjCBridge](https://github.com/Danie1s/TiercelObjCBridge) 进行桥接 + +- [Tiercel 3.0](#tiercel-30) +- [特性](#特性) +- [环境要求](#环境要求) +- [集成](#集成) +- [Demo](#demo) +- [用法](#用法) + - [基本用法](#基本用法) + - [后台下载](#后台下载) + - [文件校验](#文件校验) + - [更多](#更多) +- [License](#license) + + + +## Tiercel 3.0 + +Tiercel 3.0 大幅提高了性能,拥有更完善的错误处理,提供了更多方便的 API。从 Tiercel 2.0 升级到 Tiercel 3.0 是很简单的,强烈推荐所有开发者都进行升级,具体请查看 [Tiercel 3.0 迁移指南](https://github.com/Danie1s/Tiercel/wiki/Tiercel-3.0-%E8%BF%81%E7%A7%BB%E6%8C%87%E5%8D%97) + +## 特性 + +- [x] 支持原生级别的后台下载 +- [x] 支持离线断点续传,App 无论 crash 还是被手动 Kill 都可以恢复下载 +- [x] 拥有精细的任务管理,每个下载任务都可以单独操作和管理 +- [x] 支持创建多个下载模块,每个模块互不影响 +- [x] 每个下载模块拥有单独的管理者,可以对总任务进行操作和管理 +- [x] 支持批量操作 +- [x] 内置了下载速度、剩余时间等常见的下载信息 +- [x] 支持自定义日志 +- [x] 支持下载任务排序 +- [x] 链式语法调用 +- [x] 支持控制下载任务的最大并发数 +- [x] 支持文件校验 +- [x] 线程安全 + + + +## 环境要求 + +- iOS 10.0+ +- Xcode 11.0+ +- Swift 5.0+ + + + +## 安装 + +### CocoaPods + +Tiercel 支持 CocoaPods 集成,首先需要使用以下命令安装 CocoaPod: + +```bash +$ gem install cocoapods +``` + +在`Podfile`文件中 + +```ruby +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '10.0' +use_frameworks! + +target '' do + pod 'Tiercel' +end +``` + +最后运行命令 + +```bash +$ pod install +``` + +### Swift Package Manager + +从 Xcode 11 开始,集成了 Swift Package Manager,使用起来非常方便。Tiercel 也支持通过 Swift Package Manager 集成。 + +在 Xcode 的菜单栏中选择 `File > Swift Packages > Add Pacakage Dependency`,然后在搜索栏输入 + +`git@github.com:Danie1s/Tiercel.git`,即可完成集成 + +### 手动集成 + +Tiercel 也支持手动集成,只需把本项目文件夹中的`Tiercel`文件夹拖进需要集成的项目即可 + + + +## Demo + +打开本项目文件夹中 `Tiercel.xcodeproj` ,可以直接运行 Demo + + + + + +## 用法 + +### 基本用法 + +一行代码开启下载 + +```swift +// 创建下载任务并且开启下载,同时返回可选类型的DownloadTask实例,如果url无效,则返回nil +let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg") + +// 批量创建下载任务并且开启下载,返回有效url对应的任务数组,urls需要跟fileNames一一对应 +let tasks = sessionManager.multiDownload(URLStrings) +``` + +可以对任务设置状态回调 + +```swift +let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg") + +task?.progress(onMainQueue: true) { (task) in + let progress = task.progress.fractionCompleted + print("下载中, 进度:\(progress)") +}.success { (task) in + print("下载完成") +}.failure { (task) in + print("下载失败") +} +``` + +可以通过 URL 对下载任务进行操作,也可以直接操作下载任务 + +```swift +let URLString = "http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg" + +// 通过 URL 对下载任务进行操作 +sessionManager.start(URLString) +sessionManager.suspend(URLString) +sessionManager.cancel(URLString) +sessionManager.remove(URLString, completely: false) + +// 直接对下载任务进行操作 +sessionManager.start(task) +sessionManager.suspend(task) +sessionManager.cancel(task) +sessionManager.remove(task, completely: false) +``` + + + +### 后台下载 + +从 Tiercel 2.0 开始支持原生的后台下载,只要使用 Tiercel 开启了下载任务: + +- 手动 Kill App,任务会暂停,重启 App 后可以恢复进度,继续下载 +- 只要不是手动 Kill App,任务都会一直在下载,例如: + - App 退回后台 + - App 崩溃或者被系统关闭 + - 重启手机 + +如果想了解后台下载的细节和注意事项,可以查看:[iOS 原生级别后台下载详解](https://github.com/Danie1s/Tiercel/wiki/iOS-%E5%8E%9F%E7%94%9F%E7%BA%A7%E5%88%AB%E5%90%8E%E5%8F%B0%E4%B8%8B%E8%BD%BD%E8%AF%A6%E8%A7%A3) + + + +### 文件校验 + +Tiercel 提供了文件校验功能,可以根据需要添加,校验结果在回调的`task.validation`里 + +```swift + +let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg") +// 回调闭包可以选择是否在主线程上执行 +task?.validateFile(code: "9e2a3650530b563da297c9246acaad5c", + type: .md5, + onMainQueue: true) + { (task) in + if task.validation == .correct { + // 文件正确 + } else { + // 文件错误 + } +} +``` + + + +### 更多 + +有关 Tiercel 3.0 的详细使用方法和升级迁移,请查看 [Wiki](https://github.com/Danie1s/Tiercel/wiki) + + + + +## License + +Tiercel is available under the MIT license. See the LICENSE file for more info. + + diff --git a/Pods/Tiercel/Sources/Extensions/Array+Safe.swift b/Pods/Tiercel/Sources/Extensions/Array+Safe.swift new file mode 100644 index 0000000..d22a212 --- /dev/null +++ b/Pods/Tiercel/Sources/Extensions/Array+Safe.swift @@ -0,0 +1,38 @@ +// +// Array+Safe.swift +// Tiercel +// +// Created by Daniels on 2019/1/22. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + + +extension Array { + public func safeObject(at index: Int) -> Element? { + if (0..()) { + if Thread.isMainThread { + block() + } else { + DispatchQueue.main.async { + block() + } + } + } + +} diff --git a/Pods/Tiercel/Sources/Extensions/Double+TaskInfo.swift b/Pods/Tiercel/Sources/Extensions/Double+TaskInfo.swift new file mode 100644 index 0000000..ac052b8 --- /dev/null +++ b/Pods/Tiercel/Sources/Extensions/Double+TaskInfo.swift @@ -0,0 +1,42 @@ +// +// Double+TaskInfo.swift +// Tiercel +// +// Created by Daniels on 2019/1/22. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + + +extension Double: TiercelCompatible {} +extension TiercelWrapper where Base == Double { + /// 返回 yyyy-MM-dd HH:mm:ss格式的字符串 + /// + /// - Returns: + public func convertTimeToDateString() -> String { + let date = Date(timeIntervalSince1970: base) + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return formatter.string(from: date) + } + +} diff --git a/Pods/Tiercel/Sources/Extensions/FileManager+AvailableCapacity.swift b/Pods/Tiercel/Sources/Extensions/FileManager+AvailableCapacity.swift new file mode 100644 index 0000000..2bec7ae --- /dev/null +++ b/Pods/Tiercel/Sources/Extensions/FileManager+AvailableCapacity.swift @@ -0,0 +1,48 @@ +// +// FileManager+AvailableCapacity.swift +// Tiercel +// +// Created by Daniels on 2019/1/22. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension FileManager: TiercelCompatible {} +extension TiercelWrapper where Base: FileManager { + public var freeDiskSpaceInBytes: Int64 { + if #available(macOS 10.13, iOS 11.0, *) { + if let space = try? URL(fileURLWithPath: NSHomeDirectory()).resourceValues(forKeys: [URLResourceKey.volumeAvailableCapacityForImportantUsageKey]).volumeAvailableCapacityForImportantUsage { + return space + } else { + return 0 + } + } else { + if let systemAttributes = try? base.attributesOfFileSystem(forPath: NSHomeDirectory()), + let freeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value { + return freeSpace + } else { + return 0 + } + } + } +} + diff --git a/Pods/Tiercel/Sources/Extensions/Int64+TaskInfo.swift b/Pods/Tiercel/Sources/Extensions/Int64+TaskInfo.swift new file mode 100644 index 0000000..d709cd9 --- /dev/null +++ b/Pods/Tiercel/Sources/Extensions/Int64+TaskInfo.swift @@ -0,0 +1,59 @@ +// +// Int64+TaskInfo.swift +// Tiercel +// +// Created by Daniels on 2019/1/22. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension Int64: TiercelCompatible {} +extension TiercelWrapper where Base == Int64 { + + /// 返回下载速度的字符串,如:1MB/s + /// + /// - Returns: + public func convertSpeedToString() -> String { + let size = convertBytesToString() + return [size, "s"].joined(separator: "/") + } + + /// 返回 00:00格式的字符串 + /// + /// - Returns: + public func convertTimeToString() -> String { + let formatter = DateComponentsFormatter() + + formatter.unitsStyle = .positional + + return formatter.string(from: TimeInterval(base)) ?? "" + } + + /// 返回字节大小的字符串 + /// + /// - Returns: + public func convertBytesToString() -> String { + return ByteCountFormatter.string(fromByteCount: base, countStyle: .file) + } + + +} diff --git a/Pods/Tiercel/Sources/Extensions/OperationQueue+DispatchQueue.swift b/Pods/Tiercel/Sources/Extensions/OperationQueue+DispatchQueue.swift new file mode 100644 index 0000000..324baf0 --- /dev/null +++ b/Pods/Tiercel/Sources/Extensions/OperationQueue+DispatchQueue.swift @@ -0,0 +1,40 @@ +// +// OperationQueue+DispatchQueue.swift +// Tiercel +// +// Created by Daniels on 2019/4/30. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension OperationQueue { + convenience init(qualityOfService: QualityOfService = .default, + maxConcurrentOperationCount: Int = OperationQueue.defaultMaxConcurrentOperationCount, + underlyingQueue: DispatchQueue? = nil, + name: String? = nil) { + self.init() + self.qualityOfService = qualityOfService + self.maxConcurrentOperationCount = maxConcurrentOperationCount + self.underlyingQueue = underlyingQueue + self.name = name + } +} diff --git a/Pods/Tiercel/Sources/Extensions/String+Hash.swift b/Pods/Tiercel/Sources/Extensions/String+Hash.swift new file mode 100644 index 0000000..71eb40f --- /dev/null +++ b/Pods/Tiercel/Sources/Extensions/String+Hash.swift @@ -0,0 +1,59 @@ +// +// String+Hash.swift +// Tiercel +// +// Created by Daniels on 2019/1/22. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + + +extension String: TiercelCompatible { } +extension TiercelWrapper where Base == String { + public var md5: String { + guard let data = base.data(using: .utf8) else { + return base + } + return data.tr.md5 + } + + public var sha1: String { + guard let data = base.data(using: .utf8) else { + return base + } + return data.tr.sha1 + } + + public var sha256: String { + guard let data = base.data(using: .utf8) else { + return base + } + return data.tr.sha256 + } + + public var sha512: String { + guard let data = base.data(using: .utf8) else { + return base + } + return data.tr.sha512 + } +} diff --git a/Pods/Tiercel/Sources/Extensions/URLSession+ResumeData.swift b/Pods/Tiercel/Sources/Extensions/URLSession+ResumeData.swift new file mode 100644 index 0000000..946c9cf --- /dev/null +++ b/Pods/Tiercel/Sources/Extensions/URLSession+ResumeData.swift @@ -0,0 +1,50 @@ +// +// URLSession+ResumeData.swift +// Tiercel +// +// Created by Daniels on 2019/1/22. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension URLSession { + + /// 把有bug的resumeData修复,然后创建task + /// + /// - Parameter resumeData: + /// - Returns: + internal func correctedDownloadTask(withResumeData resumeData: Data) -> URLSessionDownloadTask { + + let task = downloadTask(withResumeData: resumeData) + + if let resumeDictionary = ResumeDataHelper.getResumeDictionary(resumeData) { + if task.originalRequest == nil, let originalReqData = resumeDictionary[ResumeDataHelper.originalRequestKey] as? Data, let originalRequest = NSKeyedUnarchiver.unarchiveObject(with: originalReqData) as? NSURLRequest { + task.setValue(originalRequest, forKey: "originalRequest") + } + if task.currentRequest == nil, let currentReqData = resumeDictionary[ResumeDataHelper.currentRequestKey] as? Data, let currentRequest = NSKeyedUnarchiver.unarchiveObject(with: currentReqData) as? NSURLRequest { + task.setValue(currentRequest, forKey: "currentRequest") + } + } + + return task + } +} diff --git a/Pods/Tiercel/Sources/General/Cache.swift b/Pods/Tiercel/Sources/General/Cache.swift new file mode 100644 index 0000000..ebd8240 --- /dev/null +++ b/Pods/Tiercel/Sources/General/Cache.swift @@ -0,0 +1,389 @@ +// +// Cache.swift +// Tiercel +// +// Created by Daniels on 2018/3/16. +// Copyright © 2018 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public class Cache { + + private let ioQueue: DispatchQueue + + private var debouncer: Debouncer + + public let downloadPath: String + + public let downloadTmpPath: String + + public let downloadFilePath: String + + public let identifier: String + + private let fileManager = FileManager.default + + private let encoder = PropertyListEncoder() + + internal weak var manager: SessionManager? + + private let decoder = PropertyListDecoder() + + public static func defaultDiskCachePathClosure(_ cacheName: String) -> String { + let dstPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + return (dstPath as NSString).appendingPathComponent(cacheName) + } + + + /// 初始化方法 + /// - Parameters: + /// - identifier: 不同的identifier代表不同的下载模块。如果没有自定义下载目录,Cache会提供默认的目录,这些目录跟identifier相关 + /// - downloadPath: 存放用于DownloadTask持久化的数据,默认提供的downloadTmpPath、downloadFilePath也是在里面 + /// - downloadTmpPath: 存放下载中的临时文件 + /// - downloadFilePath: 存放下载完成后的文件 + public init(_ identifier: String, downloadPath: String? = nil, downloadTmpPath: String? = nil, downloadFilePath: String? = nil) { + self.identifier = identifier + + let ioQueueName = "com.Tiercel.Cache.ioQueue.\(identifier)" + ioQueue = DispatchQueue(label: ioQueueName, autoreleaseFrequency: .workItem) + + debouncer = Debouncer(queue: ioQueue) + + let cacheName = "com.Daniels.Tiercel.Cache.\(identifier)" + + let diskCachePath = Cache.defaultDiskCachePathClosure(cacheName) + + let path = downloadPath ?? (diskCachePath as NSString).appendingPathComponent("Downloads") + + self.downloadPath = path + + self.downloadTmpPath = downloadTmpPath ?? (path as NSString).appendingPathComponent("Tmp") + + self.downloadFilePath = downloadFilePath ?? (path as NSString).appendingPathComponent("File") + + createDirectory() + + decoder.userInfo[.cache] = self + + } + + public func invalidate() { + decoder.userInfo[.cache] = nil + } +} + + +// MARK: - file +extension Cache { + internal func createDirectory() { + + if !fileManager.fileExists(atPath: downloadPath) { + do { + try fileManager.createDirectory(atPath: downloadPath, withIntermediateDirectories: true, attributes: nil) + } catch { + manager?.log(.error("create directory failed", + error: TiercelError.cacheError(reason: .cannotCreateDirectory(path: downloadPath, + error: error)))) + } + } + + if !fileManager.fileExists(atPath: downloadTmpPath) { + do { + try fileManager.createDirectory(atPath: downloadTmpPath, withIntermediateDirectories: true, attributes: nil) + } catch { + manager?.log(.error("create directory failed", + error: TiercelError.cacheError(reason: .cannotCreateDirectory(path: downloadTmpPath, + error: error)))) + } + } + + if !fileManager.fileExists(atPath: downloadFilePath) { + do { + try fileManager.createDirectory(atPath: downloadFilePath, withIntermediateDirectories: true, attributes: nil) + } catch { + manager?.log(.error("create directory failed", + error: TiercelError.cacheError(reason: .cannotCreateDirectory(path: downloadFilePath, + error: error)))) + } + } + } + + + public func filePath(fileName: String) -> String? { + if fileName.isEmpty { + return nil + } + let path = (downloadFilePath as NSString).appendingPathComponent(fileName) + return path + } + + public func fileURL(fileName: String) -> URL? { + guard let path = filePath(fileName: fileName) else { return nil } + return URL(fileURLWithPath: path) + } + + public func fileExists(fileName: String) -> Bool { + guard let path = filePath(fileName: fileName) else { return false } + return fileManager.fileExists(atPath: path) + } + + public func filePath(url: URLConvertible) -> String? { + do { + let validURL = try url.asURL() + let fileName = validURL.tr.fileName + return filePath(fileName: fileName) + } catch { + return nil + } + } + + public func fileURL(url: URLConvertible) -> URL? { + guard let path = filePath(url: url) else { return nil } + return URL(fileURLWithPath: path) + } + + public func fileExists(url: URLConvertible) -> Bool { + guard let path = filePath(url: url) else { return false } + return fileManager.fileExists(atPath: path) + } + + + + public func clearDiskCache(onMainQueue: Bool = true, handler: Handler? = nil) { + ioQueue.async { + guard self.fileManager.fileExists(atPath: self.downloadPath) else { return } + do { + try self.fileManager.removeItem(atPath: self.downloadPath) + } catch { + self.manager?.log(.error("clear disk cache failed", + error: TiercelError.cacheError(reason: .cannotRemoveItem(path: self.downloadPath, + error: error)))) + } + self.createDirectory() + if let handler = handler { + Executer(onMainQueue: onMainQueue, handler: handler).execute(self) + } + } + } +} + + +// MARK: - retrieve +extension Cache { + internal func retrieveAllTasks() -> [DownloadTask] { + return ioQueue.sync { + let path = (downloadPath as NSString).appendingPathComponent("\(identifier)_Tasks.plist") + if fileManager.fileExists(atPath: path) { + do { + let url = URL(fileURLWithPath: path) + let data = try Data(contentsOf: url) + let tasks = try decoder.decode([DownloadTask].self, from: data) + tasks.forEach { (task) in + task.cache = self + if task.status == .waiting { + task.protectedState.write { $0.status = .suspended } + } + } + return tasks + } catch { + manager?.log(.error("retrieve all tasks failed", error: TiercelError.cacheError(reason: .cannotRetrieveAllTasks(path: path, error: error)))) + return [DownloadTask]() + } + } else { + return [DownloadTask]() + } + } + } + + internal func retrieveTmpFile(_ tmpFileName: String?) -> Bool { + return ioQueue.sync { + guard let tmpFileName = tmpFileName, !tmpFileName.isEmpty else { return false } + let backupFilePath = (downloadTmpPath as NSString).appendingPathComponent(tmpFileName) + let originFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(tmpFileName) + let backupFileExists = fileManager.fileExists(atPath: backupFilePath) + let originFileExists = fileManager.fileExists(atPath: originFilePath) + guard backupFileExists || originFileExists else { return false } + + if originFileExists { + do { + try fileManager.removeItem(atPath: backupFilePath) + } catch { + self.manager?.log(.error("retrieve tmpFile failed", + error: TiercelError.cacheError(reason: .cannotRemoveItem(path: backupFilePath, + error: error)))) + } + } else { + do { + try fileManager.moveItem(atPath: backupFilePath, toPath: originFilePath) + } catch { + self.manager?.log(.error("retrieve tmpFile failed", + error: TiercelError.cacheError(reason: .cannotMoveItem(atPath: backupFilePath, + toPath: originFilePath, + error: error)))) + } + } + return true + } + + } + + +} + + +// MARK: - store +extension Cache { + internal func storeTasks(_ tasks: [DownloadTask]) { + debouncer.execute(label: "storeTasks", wallDeadline: .now() + 0.2) { + var path = (self.downloadPath as NSString).appendingPathComponent("\(self.identifier)_Tasks.plist") + do { + let data = try self.encoder.encode(tasks) + let url = URL(fileURLWithPath: path) + try data.write(to: url) + } catch { + self.manager?.log(.error("store tasks failed", + error: TiercelError.cacheError(reason: .cannotEncodeTasks(path: path, + error: error)))) + } + path = (self.downloadPath as NSString).appendingPathComponent("\(self.identifier)Tasks.plist") + try? self.fileManager.removeItem(atPath: path) + } + } + + internal func storeFile(at srcURL: URL, to dstURL: URL) { + ioQueue.sync { + do { + try fileManager.moveItem(at: srcURL, to: dstURL) + } catch { + self.manager?.log(.error("store file failed", + error: TiercelError.cacheError(reason: .cannotMoveItem(atPath: srcURL.absoluteString, + toPath: dstURL.absoluteString, + error: error)))) + } + } + } + + internal func storeTmpFile(_ tmpFileName: String?) { + ioQueue.sync { + guard let tmpFileName = tmpFileName, !tmpFileName.isEmpty else { return } + let tmpPath = (NSTemporaryDirectory() as NSString).appendingPathComponent(tmpFileName) + let destination = (downloadTmpPath as NSString).appendingPathComponent(tmpFileName) + if fileManager.fileExists(atPath: destination) { + do { + try fileManager.removeItem(atPath: destination) + } catch { + self.manager?.log(.error("store tmpFile failed", + error: TiercelError.cacheError(reason: .cannotRemoveItem(path: destination, + error: error)))) + } + } + if fileManager.fileExists(atPath: tmpPath) { + do { + try fileManager.copyItem(atPath: tmpPath, toPath: destination) + } catch { + self.manager?.log(.error("store tmpFile failed", + error: TiercelError.cacheError(reason: .cannotCopyItem(atPath: tmpPath, + toPath: destination, + error: error)))) + } + } + } + } + + internal func updateFileName(_ filePath: String, _ newFileName: String) { + ioQueue.sync { + if fileManager.fileExists(atPath: filePath) { + let newFilePath = self.filePath(fileName: newFileName)! + do { + try fileManager.moveItem(atPath: filePath, toPath: newFilePath) + } catch { + self.manager?.log(.error("update fileName failed", + error: TiercelError.cacheError(reason: .cannotMoveItem(atPath: filePath, + toPath: newFilePath, + error: error)))) + } + } + } + } +} + + +// MARK: - remove +extension Cache { + internal func remove(_ task: DownloadTask, completely: Bool) { + removeTmpFile(task.tmpFileName) + + if completely { + removeFile(task.filePath) + } + } + + internal func removeFile(_ filePath: String) { + ioQueue.async { + if self.fileManager.fileExists(atPath: filePath) { + do { + try self.fileManager.removeItem(atPath: filePath) + } catch { + self.manager?.log(.error("remove file failed", + error: TiercelError.cacheError(reason: .cannotRemoveItem(path: filePath, + error: error)))) + } + } + } + } + + + + /// 删除保留在本地的缓存文件 + /// + /// - Parameter task: + internal func removeTmpFile(_ tmpFileName: String?) { + ioQueue.async { + guard let tmpFileName = tmpFileName, !tmpFileName.isEmpty else { return } + let path1 = (self.downloadTmpPath as NSString).appendingPathComponent(tmpFileName) + let path2 = (NSTemporaryDirectory() as NSString).appendingPathComponent(tmpFileName) + [path1, path2].forEach { (path) in + if self.fileManager.fileExists(atPath: path) { + do { + try self.fileManager.removeItem(atPath: path) + } catch { + self.manager?.log(.error("remove tmpFile failed", + error: TiercelError.cacheError(reason: .cannotRemoveItem(path: path, + error: error)))) + } + } + } + + } + } +} + +extension URL: TiercelCompatible { } +extension TiercelWrapper where Base == URL { + public var fileName: String { + var fileName = base.absoluteString.tr.md5 + if !base.pathExtension.isEmpty { + fileName += ".\(base.pathExtension)" + } + return fileName + } +} diff --git a/Pods/Tiercel/Sources/General/Common.swift b/Pods/Tiercel/Sources/General/Common.swift new file mode 100644 index 0000000..4425d0b --- /dev/null +++ b/Pods/Tiercel/Sources/General/Common.swift @@ -0,0 +1,111 @@ +// +// Common.swift +// Tiercel +// +// Created by Daniels on 2018/3/16. +// Copyright © 2018 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + + +public enum LogOption { + case `default` + case none +} + +public enum LogType { + case sessionManager(_ message: String, manager: SessionManager) + case downloadTask(_ message: String, task: DownloadTask) + case error(_ message: String, error: Error) +} + +public protocol Logable { + var identifier: String { get } + + var option: LogOption { get set } + + func log(_ type: LogType) +} + +public struct Logger: Logable { + + public let identifier: String + + public var option: LogOption + + public func log(_ type: LogType) { + guard option == .default else { return } + var strings = ["************************ TiercelLog ************************"] + strings.append("identifier : \(identifier)") + switch type { + case let .sessionManager(message, manager): + strings.append("Message : [SessionManager] \(message), tasks.count: \(manager.tasks.count)") + case let .downloadTask(message, task): + strings.append("Message : [DownloadTask] \(message)") + strings.append("Task URL : \(task.url.absoluteString)") + if let error = task.error, task.status == .failed { + strings.append("Error : \(error)") + } + case let .error(message, error): + strings.append("Message : [Error] \(message)") + strings.append("Description : \(error)") + } + strings.append("") + print(strings.joined(separator: "\n")) + } +} + +public enum Status: String { + case waiting + case running + case suspended + case canceled + case failed + case removed + case succeeded + + case willSuspend + case willCancel + case willRemove +} + +public struct TiercelWrapper { + internal let base: Base + internal init(_ base: Base) { + self.base = base + } +} + + +public protocol TiercelCompatible { + +} + +extension TiercelCompatible { + public var tr: TiercelWrapper { + get { TiercelWrapper(self) } + } + public static var tr: TiercelWrapper.Type { + get { TiercelWrapper.self } + } +} + diff --git a/Pods/Tiercel/Sources/General/DownloadTask.swift b/Pods/Tiercel/Sources/General/DownloadTask.swift new file mode 100644 index 0000000..4a004bf --- /dev/null +++ b/Pods/Tiercel/Sources/General/DownloadTask.swift @@ -0,0 +1,606 @@ +// +// DownloadTask.swift +// Tiercel +// +// Created by Daniels on 2018/3/16. +// Copyright © 2018 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +public class DownloadTask: Task { + + private enum CodingKeys: CodingKey { + case resumeData + case response + } + + private var acceptableStatusCodes: Range { return 200..<300 } + + private var _sessionTask: URLSessionDownloadTask? { + willSet { + _sessionTask?.removeObserver(self, forKeyPath: "currentRequest") + } + didSet { + _sessionTask?.addObserver(self, forKeyPath: "currentRequest", options: [.new], context: nil) + } + } + + internal var sessionTask: URLSessionDownloadTask? { + get { protectedDownloadState.read { _ in _sessionTask }} + set { protectedDownloadState.write { _ in _sessionTask = newValue }} + } + + + public private(set) var response: HTTPURLResponse? { + get { protectedDownloadState.wrappedValue.response } + set { protectedDownloadState.write { $0.response = newValue } } + } + + + public var filePath: String { + return cache.filePath(fileName: fileName)! + } + + public var pathExtension: String? { + let pathExtension = (filePath as NSString).pathExtension + return pathExtension.isEmpty ? nil : pathExtension + } + + + private struct DownloadState { + var resumeData: Data? { + didSet { + guard let resumeData = resumeData else { return } + tmpFileName = ResumeDataHelper.getTmpFileName(resumeData) + } + } + var response: HTTPURLResponse? + var tmpFileName: String? + var shouldValidateFile: Bool = false + } + + private let protectedDownloadState: Protected = Protected(DownloadState()) + + + private var resumeData: Data? { + get { protectedDownloadState.wrappedValue.resumeData } + set { protectedDownloadState.write { $0.resumeData = newValue } } + } + + internal var tmpFileName: String? { + protectedDownloadState.wrappedValue.tmpFileName + } + + private var shouldValidateFile: Bool { + get { protectedDownloadState.wrappedValue.shouldValidateFile } + set { protectedDownloadState.write { $0.shouldValidateFile = newValue } } + } + + + internal init(_ url: URL, + headers: [String: String]? = nil, + fileName: String? = nil, + cache: Cache, + operationQueue: DispatchQueue) { + super.init(url, + headers: headers, + cache: cache, + operationQueue: operationQueue) + if let fileName = fileName, !fileName.isEmpty { + self.fileName = fileName + } + NotificationCenter.default.addObserver(self, + selector: #selector(fixDelegateMethodError), + name: UIApplication.didBecomeActiveNotification, + object: nil) + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + let superEncoder = container.superEncoder() + try super.encode(to: superEncoder) + try container.encodeIfPresent(resumeData, forKey: .resumeData) + if let response = response { + let responseData: Data + if #available(iOS 11.0, *) { + responseData = try NSKeyedArchiver.archivedData(withRootObject: (response as HTTPURLResponse), requiringSecureCoding: true) + } else { + responseData = NSKeyedArchiver.archivedData(withRootObject: (response as HTTPURLResponse)) + } + try container.encode(responseData, forKey: .response) + } + } + + internal required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let superDecoder = try container.superDecoder() + try super.init(from: superDecoder) + resumeData = try container.decodeIfPresent(Data.self, forKey: .resumeData) + if let responseData = try container.decodeIfPresent(Data.self, forKey: .response) { + if #available(iOS 11.0, *) { + response = try? NSKeyedUnarchiver.unarchivedObject(ofClass: HTTPURLResponse.self, from: responseData) + } else { + response = NSKeyedUnarchiver.unarchiveObject(with: responseData) as? HTTPURLResponse + } + } + } + + + deinit { + sessionTask?.removeObserver(self, forKeyPath: "currentRequest") + NotificationCenter.default.removeObserver(self) + } + + @objc private func fixDelegateMethodError() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.sessionTask?.suspend() + self.sessionTask?.resume() + } + } + + + internal override func execute(_ executer: Executer?) { + executer?.execute(self) + } + + +} + + +// MARK: - control +extension DownloadTask { + + internal func download() { + cache.createDirectory() + guard let manager = manager else { return } + switch status { + case .waiting, .suspended, .failed: + if cache.fileExists(fileName: fileName) { + prepareForDownload(fileExists: true) + } else { + if manager.shouldRun { + prepareForDownload(fileExists: false) + } else { + status = .waiting + progressExecuter?.execute(self) + executeControl() + } + } + case .succeeded: + executeControl() + succeeded(fromRunning: false, immediately: false) + case .running: + status = .running + executeControl() + default: break + } + } + + private func prepareForDownload(fileExists: Bool) { + status = .running + protectedState.write { + $0.speed = 0 + if $0.startDate == 0 { + $0.startDate = Date().timeIntervalSince1970 + } + } + error = nil + response = nil + start(fileExists: fileExists) + } + + private func start(fileExists: Bool) { + if fileExists { + manager?.log(.downloadTask("file already exists", task: self)) + if let fileInfo = try? FileManager.default.attributesOfItem(atPath: cache.filePath(fileName: fileName)!), + let length = fileInfo[.size] as? Int64 { + progress.totalUnitCount = length + } + executeControl() + operationQueue.async { + self.didComplete(.local) + } + } else { + if let resumeData = resumeData, + cache.retrieveTmpFile(tmpFileName) { + if #available(iOS 10.2, *) { + sessionTask = session?.downloadTask(withResumeData: resumeData) + } else if #available(iOS 10.0, *) { + sessionTask = session?.correctedDownloadTask(withResumeData: resumeData) + } else { + sessionTask = session?.downloadTask(withResumeData: resumeData) + } + } else { + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 0) + if let headers = headers { + request.allHTTPHeaderFields = headers + } + sessionTask = session?.downloadTask(with: request) + progress.completedUnitCount = 0 + progress.totalUnitCount = 0 + } + progress.setUserInfoObject(progress.completedUnitCount, forKey: .fileCompletedCountKey) + sessionTask?.resume() + manager?.maintainTasks(with: .appendRunningTasks(self)) + manager?.storeTasks() + executeControl() + } + } + + + internal func suspend(onMainQueue: Bool = true, handler: Handler? = nil) { + guard status == .running || status == .waiting else { return } + controlExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if status == .running { + status = .willSuspend + sessionTask?.cancel(byProducingResumeData: { _ in }) + } else { + status = .willSuspend + operationQueue.async { + self.didComplete(.local) + } + } + } + + internal func cancel(onMainQueue: Bool = true, handler: Handler? = nil) { + guard status != .succeeded else { return } + controlExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if status == .running { + status = .willCancel + sessionTask?.cancel() + } else { + status = .willCancel + operationQueue.async { + self.didComplete(.local) + } + } + } + + + + internal func remove(completely: Bool = false, onMainQueue: Bool = true, handler: Handler? = nil) { + isRemoveCompletely = completely + controlExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if status == .running { + status = .willRemove + sessionTask?.cancel() + } else { + status = .willRemove + operationQueue.async { + self.didComplete(.local) + } + } + } + + + internal func update(_ newHeaders: [String: String]? = nil, newFileName: String? = nil) { + headers = newHeaders + if let newFileName = newFileName, !newFileName.isEmpty { + cache.updateFileName(filePath, newFileName) + fileName = newFileName + } + } + + private func validateFile() { + guard let validateHandler = self.validateExecuter else { return } + + if !shouldValidateFile { + validateHandler.execute(self) + return + } + + guard let verificationCode = verificationCode else { return } + + FileChecksumHelper.validateFile(filePath, code: verificationCode, type: verificationType) { [weak self] (result) in + guard let self = self else { return } + self.shouldValidateFile = false + if case let .failure(error) = result { + self.validation = .incorrect + self.manager?.log(.error("file validation failed, url: \(self.url)", error: error)) + } else { + self.validation = .correct + self.manager?.log(.downloadTask("file validation successful", task: self)) + } + self.manager?.storeTasks() + validateHandler.execute(self) + } + } + +} + + + +// MARK: - status handle +extension DownloadTask { + + private func didCancelOrRemove() { + // 把预操作的状态改成完成操作的状态 + if status == .willCancel { + status = .canceled + } + if status == .willRemove { + status = .removed + } + cache.remove(self, completely: isRemoveCompletely) + + manager?.didCancelOrRemove(self) + } + + + internal func succeeded(fromRunning: Bool, immediately: Bool) { + if endDate == 0 { + protectedState.write { + $0.endDate = Date().timeIntervalSince1970 + $0.timeRemaining = 0 + } + } + status = .succeeded + progress.completedUnitCount = progress.totalUnitCount + progressExecuter?.execute(self) + if immediately { + executeCompletion(true) + } + validateFile() + manager?.maintainTasks(with: .succeeded(self)) + manager?.determineStatus(fromRunningTask: fromRunning) + } + + + private func determineStatus(with interruptType: InterruptType) { + var fromRunning = true + switch interruptType { + case let .error(error): + self.error = error + var tempStatus = status + if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data { + self.resumeData = ResumeDataHelper.handleResumeData(resumeData) + cache.storeTmpFile(tmpFileName) + } + if let _ = (error as NSError).userInfo[NSURLErrorBackgroundTaskCancelledReasonKey] as? Int { + tempStatus = .suspended + } + if let urlError = error as? URLError, urlError.code != URLError.cancelled { + tempStatus = .failed + } + status = tempStatus + case let .statusCode(statusCode): + self.error = TiercelError.unacceptableStatusCode(code: statusCode) + status = .failed + case let .manual(fromRunningTask): + fromRunning = fromRunningTask + } + + switch status { + case .willSuspend: + status = .suspended + progressExecuter?.execute(self) + executeControl() + executeCompletion(false) + case .willCancel, .willRemove: + didCancelOrRemove() + executeControl() + executeCompletion(false) + case .suspended, .failed: + progressExecuter?.execute(self) + executeCompletion(false) + default: + status = .failed + progressExecuter?.execute(self) + executeCompletion(false) + } + manager?.determineStatus(fromRunningTask: fromRunning) + } +} + +// MARK: - closure +extension DownloadTask { + @discardableResult + public func validateFile(code: String, + type: FileChecksumHelper.VerificationType, + onMainQueue: Bool = true, + handler: @escaping Handler) -> Self { + operationQueue.async { + let (verificationCode, verificationType) = self.protectedState.read { + ($0.verificationCode, $0.verificationType) + } + if verificationCode == code && + verificationType == type && + self.validation != .unkown { + self.shouldValidateFile = false + } else { + self.shouldValidateFile = true + self.protectedState.write { + $0.verificationCode = code + $0.verificationType = type + } + self.manager?.storeTasks() + } + self.validateExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if self.status == .succeeded { + self.validateFile() + } + } + return self + } + + private func executeCompletion(_ isSucceeded: Bool) { + if let completionExecuter = completionExecuter { + completionExecuter.execute(self) + } else if isSucceeded { + successExecuter?.execute(self) + } else { + failureExecuter?.execute(self) + } + NotificationCenter.default.postNotification(name: DownloadTask.didCompleteNotification, downloadTask: self) + } + + private func executeControl() { + controlExecuter?.execute(self) + controlExecuter = nil + } +} + + + +// MARK: - KVO +extension DownloadTask { + override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if let change = change, let newRequest = change[NSKeyValueChangeKey.newKey] as? URLRequest, let url = newRequest.url { + currentURL = url + manager?.updateUrlMapper(with: self) + } + } +} + +// MARK: - info +extension DownloadTask { + + internal func updateSpeedAndTimeRemaining() { + + let dataCount = progress.completedUnitCount + let lastData: Int64 = progress.userInfo[.fileCompletedCountKey] as? Int64 ?? 0 + + if dataCount > lastData { + let speed = dataCount - lastData + updateTimeRemaining(speed) + } + progress.setUserInfoObject(dataCount, forKey: .fileCompletedCountKey) + + } + + private func updateTimeRemaining(_ speed: Int64) { + var timeRemaining: Double + if speed != 0 { + timeRemaining = (Double(progress.totalUnitCount) - Double(progress.completedUnitCount)) / Double(speed) + if timeRemaining >= 0.8 && timeRemaining < 1 { + timeRemaining += 1 + } + } else { + timeRemaining = 0 + } + protectedState.write { + $0.speed = speed + $0.timeRemaining = Int64(timeRemaining) + } + } +} + +// MARK: - callback +extension DownloadTask { + internal func didWriteData(downloadTask: URLSessionDownloadTask, bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + progress.completedUnitCount = totalBytesWritten + progress.totalUnitCount = totalBytesExpectedToWrite + response = downloadTask.response as? HTTPURLResponse + progressExecuter?.execute(self) + manager?.updateProgress() + NotificationCenter.default.postNotification(name: DownloadTask.runningNotification, downloadTask: self) + } + + + internal func didFinishDownloading(task: URLSessionDownloadTask, to location: URL) { + guard let statusCode = (task.response as? HTTPURLResponse)?.statusCode, + acceptableStatusCodes.contains(statusCode) + else { return } + cache.storeFile(at: location, to: URL(fileURLWithPath: filePath)) + cache.removeTmpFile(tmpFileName) + + } + + internal func didComplete(_ type: CompletionType) { + switch type { + case .local: + + switch status { + case .willSuspend,.willCancel, .willRemove: + determineStatus(with: .manual(false)) + case .running: + succeeded(fromRunning: false, immediately: true) + default: + return + } + + case let .network(task, error): + manager?.maintainTasks(with: .removeRunningTasks(self)) + sessionTask = nil + + switch status { + case .willCancel, .willRemove: + determineStatus(with: .manual(true)) + return + case .willSuspend, .running: + progress.totalUnitCount = task.countOfBytesExpectedToReceive + progress.completedUnitCount = task.countOfBytesReceived + progress.setUserInfoObject(task.countOfBytesReceived, forKey: .fileCompletedCountKey) + + let statusCode = (task.response as? HTTPURLResponse)?.statusCode ?? -1 + let isAcceptable = acceptableStatusCodes.contains(statusCode) + + if error != nil { + response = task.response as? HTTPURLResponse + determineStatus(with: .error(error!)) + } else if !isAcceptable { + response = task.response as? HTTPURLResponse + determineStatus(with: .statusCode(statusCode)) + } else { + resumeData = nil + succeeded(fromRunning: true, immediately: true) + } + default: + return + } + } + } + +} + + + +extension Array where Element == DownloadTask { + @discardableResult + public func progress(onMainQueue: Bool = true, handler: @escaping Handler) -> [Element] { + self.forEach { $0.progress(onMainQueue: onMainQueue, handler: handler) } + return self + } + + @discardableResult + public func success(onMainQueue: Bool = true, handler: @escaping Handler) -> [Element] { + self.forEach { $0.success(onMainQueue: onMainQueue, handler: handler) } + return self + } + + @discardableResult + public func failure(onMainQueue: Bool = true, handler: @escaping Handler) -> [Element] { + self.forEach { $0.failure(onMainQueue: onMainQueue, handler: handler) } + return self + } + + public func validateFile(codes: [String], + type: FileChecksumHelper.VerificationType, + onMainQueue: Bool = true, + handler: @escaping Handler) -> [Element] { + for (index, task) in self.enumerated() { + guard let code = codes.safeObject(at: index) else { continue } + task.validateFile(code: code, type: type, onMainQueue: onMainQueue, handler: handler) + } + return self + } +} diff --git a/Pods/Tiercel/Sources/General/Executer.swift b/Pods/Tiercel/Sources/General/Executer.swift new file mode 100644 index 0000000..8ef008c --- /dev/null +++ b/Pods/Tiercel/Sources/General/Executer.swift @@ -0,0 +1,52 @@ +// +// Executer.swift +// Tiercel +// +// Created by Daniels on 2019/4/30. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public typealias Handler = (T) -> () + +public class Executer { + private let onMainQueue: Bool + private let handler: Handler? + + public init(onMainQueue: Bool = true, handler: Handler?) { + self.onMainQueue = onMainQueue + self.handler = handler + } + + + public func execute(_ object: T) { + if let handler = handler { + if onMainQueue { + DispatchQueue.tr.executeOnMain { + handler(object) + } + } else { + handler(object) + } + } + } +} diff --git a/Pods/Tiercel/Sources/General/Notifications.swift b/Pods/Tiercel/Sources/General/Notifications.swift new file mode 100644 index 0000000..8c13332 --- /dev/null +++ b/Pods/Tiercel/Sources/General/Notifications.swift @@ -0,0 +1,82 @@ +// +// Notifications.swift +// Tiercel +// +// Created by Daniels on 2020/1/20. +// Copyright © 2020 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public extension DownloadTask { + + static let runningNotification = Notification.Name(rawValue: "com.Tiercel.notification.name.downloadTask.running") + static let didCompleteNotification = Notification.Name(rawValue: "com.Tiercel.notification.name.downloadTask.didComplete") + +} + +public extension SessionManager { + + static let runningNotification = Notification.Name(rawValue: "com.Tiercel.notification.name.sessionManager.running") + static let didCompleteNotification = Notification.Name(rawValue: "com.Tiercel.notification.name.sessionManager.didComplete") + +} + +extension Notification: TiercelCompatible { } +extension TiercelWrapper where Base == Notification { + public var downloadTask: DownloadTask? { + return base.userInfo?[String.downloadTaskKey] as? DownloadTask + } + + public var sessionManager: SessionManager? { + return base.userInfo?[String.sessionManagerKey] as? SessionManager + } +} + +extension Notification { + init(name: Notification.Name, downloadTask: DownloadTask) { + self.init(name: name, object: nil, userInfo: [String.downloadTaskKey: downloadTask]) + } + + init(name: Notification.Name, sessionManager: SessionManager) { + self.init(name: name, object: nil, userInfo: [String.sessionManagerKey: sessionManager]) + } +} + +extension NotificationCenter { + + func postNotification(name: Notification.Name, downloadTask: DownloadTask) { + let notification = Notification(name: name, downloadTask: downloadTask) + post(notification) + } + + func postNotification(name: Notification.Name, sessionManager: SessionManager) { + let notification = Notification(name: name, sessionManager: sessionManager) + post(notification) + } +} + +extension String { + + fileprivate static let downloadTaskKey = "com.Tiercel.notification.key.downloadTask" + fileprivate static let sessionManagerKey = "com.Tiercel.notification.key.sessionManagerKey" + +} diff --git a/Pods/Tiercel/Sources/General/Protected.swift b/Pods/Tiercel/Sources/General/Protected.swift new file mode 100644 index 0000000..5b72769 --- /dev/null +++ b/Pods/Tiercel/Sources/General/Protected.swift @@ -0,0 +1,190 @@ +// +// Protected.swift +// Tiercel +// +// Created by Daniels on 2020/1/9. +// Copyright © 2020 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + + +import Foundation + + +final public class UnfairLock { + private let unfairLock: os_unfair_lock_t + + public init() { + + unfairLock = .allocate(capacity: 1) + unfairLock.initialize(to: os_unfair_lock()) + } + + deinit { + unfairLock.deinitialize(count: 1) + unfairLock.deallocate() + } + + private func lock() { + os_unfair_lock_lock(unfairLock) + } + + private func unlock() { + os_unfair_lock_unlock(unfairLock) + } + + + public func around(_ closure: () throws -> T) rethrows -> T { + lock(); defer { unlock() } + return try closure() + } + + public func around(_ closure: () throws -> Void) rethrows -> Void { + lock(); defer { unlock() } + return try closure() + } +} + +@propertyWrapper +final public class Protected { + + private let lock = UnfairLock() + + private var value: T + + public var wrappedValue: T { + get { lock.around { value } } + set { lock.around { value = newValue } } + } + + public var projectedValue: Protected { self } + + + public init(_ value: T) { + self.value = value + } + + public init(wrappedValue: T) { + value = wrappedValue + } + + public func read(_ closure: (T) throws -> U) rethrows -> U { + return try lock.around { try closure(self.value) } + } + + + @discardableResult + public func write(_ closure: (inout T) throws -> U) rethrows -> U { + return try lock.around { try closure(&self.value) } + } +} + +final public class Debouncer { + + private let lock = UnfairLock() + + private let queue: DispatchQueue + + @Protected + private var workItems = [String: DispatchWorkItem]() + + public init(queue: DispatchQueue) { + self.queue = queue + } + + + public func execute(label: String, deadline: DispatchTime, execute work: @escaping @convention(block) () -> Void) { + execute(label: label, time: deadline, execute: work) + } + + + public func execute(label: String, wallDeadline: DispatchWallTime, execute work: @escaping @convention(block) () -> Void) { + execute(label: label, time: wallDeadline, execute: work) + } + + + private func execute(label: String, time: T, execute work: @escaping @convention(block) () -> Void) { + lock.around { + workItems[label]?.cancel() + let workItem = DispatchWorkItem { [weak self] in + work() + self?.workItems.removeValue(forKey: label) + } + workItems[label] = workItem + if let time = time as? DispatchTime { + queue.asyncAfter(deadline: time, execute: workItem) + } else if let time = time as? DispatchWallTime { + queue.asyncAfter(wallDeadline: time, execute: workItem) + } + } + } +} + +final public class Throttler { + + private let lock = UnfairLock() + + private let queue: DispatchQueue + + private var workItems = [String: DispatchWorkItem]() + + private let latest: Bool + + public init(queue: DispatchQueue, latest: Bool) { + self.queue = queue + self.latest = latest + } + + + public func execute(label: String, deadline: DispatchTime, execute work: @escaping @convention(block) () -> Void) { + execute(label: label, time: deadline, execute: work) + } + + + public func execute(label: String, wallDeadline: DispatchWallTime, execute work: @escaping @convention(block) () -> Void) { + execute(label: label, time: wallDeadline, execute: work) + } + + private func execute(label: String, time: T, execute work: @escaping @convention(block) () -> Void) { + lock.around { + let workItem = workItems[label] + + guard workItem == nil || latest else { return } + workItem?.cancel() + workItems[label] = DispatchWorkItem { [weak self] in + self?.workItems.removeValue(forKey: label) + work() + } + + guard workItem == nil else { return } + if let time = time as? DispatchTime { + queue.asyncAfter(deadline: time) { [weak self] in + self?.workItems[label]?.perform() + } + } else if let time = time as? DispatchWallTime { + queue.asyncAfter(wallDeadline: time) { [weak self] in + self?.workItems[label]?.perform() + } + } + } + } +} + + diff --git a/Pods/Tiercel/Sources/General/SessionConfiguration.swift b/Pods/Tiercel/Sources/General/SessionConfiguration.swift new file mode 100644 index 0000000..d3c4d9b --- /dev/null +++ b/Pods/Tiercel/Sources/General/SessionConfiguration.swift @@ -0,0 +1,64 @@ +// +// SessionConfiguration.swift +// Tiercel +// +// Created by Daniels on 2019/1/3. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public struct SessionConfiguration { + // 请求超时时间 + public var timeoutIntervalForRequest: TimeInterval = 60.0 + + private static var MaxConcurrentTasksLimit: Int = { + if #available(iOS 11.0, *) { + return 6 + } else { + return 3 + } + }() + + // 最大并发数 + private var _maxConcurrentTasksLimit: Int = MaxConcurrentTasksLimit + public var maxConcurrentTasksLimit: Int { + get { _maxConcurrentTasksLimit } + set { + let limit = min(newValue, Self.MaxConcurrentTasksLimit) + _maxConcurrentTasksLimit = max(limit, 1) + } + } + + public var allowsExpensiveNetworkAccess: Bool = true + + + public var allowsConstrainedNetworkAccess: Bool = true + + // 是否允许蜂窝网络下载 + public var allowsCellularAccess: Bool = false + + public init() { + + } +} + + diff --git a/Pods/Tiercel/Sources/General/SessionDelegate.swift b/Pods/Tiercel/Sources/General/SessionDelegate.swift new file mode 100644 index 0000000..374336f --- /dev/null +++ b/Pods/Tiercel/Sources/General/SessionDelegate.swift @@ -0,0 +1,96 @@ +// +// SessionDelegate.swift +// Tiercel +// +// Created by Daniels on 2018/3/16. +// Copyright © 2018 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +internal class SessionDelegate: NSObject { + internal weak var manager: SessionManager? + +} + + +extension SessionDelegate: URLSessionDownloadDelegate { + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { + manager?.didBecomeInvalidation(withError: error) + } + + + public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { + manager?.didFinishEvents(forBackgroundURLSession: session) + } + + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + guard let manager = manager else { return } + guard let currentURL = downloadTask.currentRequest?.url else { return } + guard let task = manager.mapTask(currentURL) else { + manager.log(.error("urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)", + error: TiercelError.fetchDownloadTaskFailed(url: currentURL)) + ) + return + } + task.didWriteData(downloadTask: downloadTask, bytesWritten: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite) + } + + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + guard let manager = manager else { return } + guard let currentURL = downloadTask.currentRequest?.url else { return } + guard let task = manager.mapTask(currentURL) else { + manager.log(.error("urlSession(_:downloadTask:didFinishDownloadingTo:)", error: TiercelError.fetchDownloadTaskFailed(url: currentURL))) + return + } + task.didFinishDownloading(task: downloadTask, to: location) + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + guard let manager = manager else { return } + if let currentURL = task.currentRequest?.url { + guard let downloadTask = manager.mapTask(currentURL) else { + manager.log(.error("urlSession(_:task:didCompleteWithError:)", error: TiercelError.fetchDownloadTaskFailed(url: currentURL))) + return + } + downloadTask.didComplete(.network(task: task, error: error)) + } else { + if let error = error { + if let urlError = error as? URLError, + let errorURL = urlError.userInfo[NSURLErrorFailingURLErrorKey] as? URL { + guard let downloadTask = manager.mapTask(errorURL) else { + manager.log(.error("urlSession(_:task:didCompleteWithError:)", error: TiercelError.fetchDownloadTaskFailed(url: errorURL))) + manager.log(.error("urlSession(_:task:didCompleteWithError:)", error: error)) + return + } + downloadTask.didComplete(.network(task: task, error: error)) + } else { + manager.log(.error("urlSession(_:task:didCompleteWithError:)", error: error)) + return + } + } else { + manager.log(.error("urlSession(_:task:didCompleteWithError:)", error: TiercelError.unknown)) + } + } + } +} diff --git a/Pods/Tiercel/Sources/General/SessionManager.swift b/Pods/Tiercel/Sources/General/SessionManager.swift new file mode 100644 index 0000000..bc315dd --- /dev/null +++ b/Pods/Tiercel/Sources/General/SessionManager.swift @@ -0,0 +1,1009 @@ +// +// SessionManager.swift +// Tiercel +// +// Created by Daniels on 2018/3/16. +// Copyright © 2018 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +public class SessionManager { + + enum MaintainTasksAction { + case append(DownloadTask) + case remove(DownloadTask) + case succeeded(DownloadTask) + case appendRunningTasks(DownloadTask) + case removeRunningTasks(DownloadTask) + } + + public let operationQueue: DispatchQueue + + public let cache: Cache + + public let identifier: String + + public var completionHandler: (() -> Void)? + + public var configuration: SessionConfiguration { + get { protectedState.wrappedValue.configuration } + set { + operationQueue.sync { + protectedState.write { + $0.configuration = newValue + if $0.status == .running { + totalSuspend() + } + } + } + } + } + + private struct State { + var logger: Logable + var isControlNetworkActivityIndicator: Bool = true + var configuration: SessionConfiguration { + didSet { + guard !shouldCreatSession else { return } + shouldCreatSession = true + if status == .running { + if configuration.maxConcurrentTasksLimit <= oldValue.maxConcurrentTasksLimit { + restartTasks = runningTasks + tasks.filter { $0.status == .waiting } + } else { + restartTasks = tasks.filter { $0.status == .waiting || $0.status == .running } + } + } else { + session?.invalidateAndCancel() + session = nil + } + } + } + var session: URLSession? + var shouldCreatSession: Bool = false + var timer: DispatchSourceTimer? + var status: Status = .waiting + var tasks: [DownloadTask] = [] + var taskMapper: [String: DownloadTask] = [String: DownloadTask]() + var urlMapper: [URL: URL] = [URL: URL]() + var runningTasks: [DownloadTask] = [] + var restartTasks: [DownloadTask] = [] + var succeededTasks: [DownloadTask] = [] + var speed: Int64 = 0 + var timeRemaining: Int64 = 0 + + var progressExecuter: Executer? + var successExecuter: Executer? + var failureExecuter: Executer? + var completionExecuter: Executer? + var controlExecuter: Executer? + } + + + private let protectedState: Protected + + public var logger: Logable { + get { protectedState.wrappedValue.logger } + set { protectedState.write { $0.logger = newValue } } + } + + public var isControlNetworkActivityIndicator: Bool { + get { protectedState.wrappedValue.isControlNetworkActivityIndicator } + set { protectedState.write { $0.isControlNetworkActivityIndicator = newValue } } + } + + + internal var shouldRun: Bool { + return runningTasks.count < configuration.maxConcurrentTasksLimit + } + + private var session: URLSession? { + get { protectedState.wrappedValue.session } + set { protectedState.write { $0.session = newValue } } + } + + private var shouldCreatSession: Bool { + get { protectedState.wrappedValue.shouldCreatSession } + set { protectedState.write { $0.shouldCreatSession = newValue } } + } + + + private var timer: DispatchSourceTimer? { + get { protectedState.wrappedValue.timer } + set { protectedState.write { $0.timer = newValue } } + } + + + public private(set) var status: Status { + get { protectedState.wrappedValue.status } + set { + protectedState.write { $0.status = newValue } + if newValue == .willSuspend || newValue == .willCancel || newValue == .willRemove { + return + } + log(.sessionManager(newValue.rawValue, manager: self)) + } + } + + + public private(set) var tasks: [DownloadTask] { + get { protectedState.wrappedValue.tasks } + set { protectedState.write { $0.tasks = newValue } } + } + + private var runningTasks: [DownloadTask] { + get { protectedState.wrappedValue.runningTasks } + set { protectedState.write { $0.runningTasks = newValue } } + } + + private var restartTasks: [DownloadTask] { + get { protectedState.wrappedValue.restartTasks } + set { protectedState.write { $0.restartTasks = newValue } } + } + + public private(set) var succeededTasks: [DownloadTask] { + get { protectedState.wrappedValue.succeededTasks } + set { protectedState.write { $0.succeededTasks = newValue } } + } + + private let _progress = Progress() + public var progress: Progress { + _progress.completedUnitCount = tasks.reduce(0, { $0 + $1.progress.completedUnitCount }) + _progress.totalUnitCount = tasks.reduce(0, { $0 + $1.progress.totalUnitCount }) + return _progress + } + + public private(set) var speed: Int64 { + get { protectedState.wrappedValue.speed } + set { protectedState.write { $0.speed = newValue } } + } + + public var speedString: String { + speed.tr.convertSpeedToString() + } + + + public private(set) var timeRemaining: Int64 { + get { protectedState.wrappedValue.timeRemaining } + set { protectedState.write { $0.timeRemaining = newValue } } + } + + public var timeRemainingString: String { + timeRemaining.tr.convertTimeToString() + } + + private var progressExecuter: Executer? { + get { protectedState.wrappedValue.progressExecuter } + set { protectedState.write { $0.progressExecuter = newValue } } + } + + private var successExecuter: Executer? { + get { protectedState.wrappedValue.successExecuter } + set { protectedState.write { $0.successExecuter = newValue } } + } + + private var failureExecuter: Executer? { + get { protectedState.wrappedValue.failureExecuter } + set { protectedState.write { $0.failureExecuter = newValue } } + } + + private var completionExecuter: Executer? { + get { protectedState.wrappedValue.completionExecuter } + set { protectedState.write { $0.completionExecuter = newValue } } + } + + private var controlExecuter: Executer? { + get { protectedState.wrappedValue.controlExecuter } + set { protectedState.write { $0.controlExecuter = newValue } } + } + + + + public init(_ identifier: String, + configuration: SessionConfiguration, + logger: Logable? = nil, + cache: Cache? = nil, + operationQueue: DispatchQueue = DispatchQueue(label: "com.Tiercel.SessionManager.operationQueue", + autoreleaseFrequency: .workItem)) { + let bundleIdentifier = Bundle.main.bundleIdentifier ?? "com.Daniels.Tiercel" + self.identifier = "\(bundleIdentifier).\(identifier)" + protectedState = Protected( + State(logger: logger ?? Logger(identifier: "\(bundleIdentifier).\(identifier)", option: .default), + configuration: configuration) + ) + self.operationQueue = operationQueue + self.cache = cache ?? Cache(identifier) + self.cache.manager = self + self.cache.retrieveAllTasks().forEach { maintainTasks(with: .append($0)) } + succeededTasks = tasks.filter { $0.status == .succeeded } + log(.sessionManager("retrieveTasks", manager: self)) + protectedState.write { state in + state.tasks.forEach { + $0.manager = self + $0.operationQueue = operationQueue + state.urlMapper[$0.currentURL] = $0.url + } + state.shouldCreatSession = true + } + operationQueue.sync { + createSession() + restoreStatus() + } + } + + deinit { + invalidate() + } + + public func invalidate() { + session?.invalidateAndCancel() + session = nil + cache.invalidate() + invalidateTimer() + } + + + private func createSession(_ completion: (() -> ())? = nil) { + guard shouldCreatSession else { return } + let sessionConfiguration = URLSessionConfiguration.background(withIdentifier: identifier) + sessionConfiguration.timeoutIntervalForRequest = configuration.timeoutIntervalForRequest + sessionConfiguration.httpMaximumConnectionsPerHost = 100000 + sessionConfiguration.allowsCellularAccess = configuration.allowsCellularAccess + if #available(iOS 13, macOS 10.15, *) { + sessionConfiguration.allowsConstrainedNetworkAccess = configuration.allowsConstrainedNetworkAccess + sessionConfiguration.allowsExpensiveNetworkAccess = configuration.allowsExpensiveNetworkAccess + } + let sessionDelegate = SessionDelegate() + sessionDelegate.manager = self + let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, + underlyingQueue: operationQueue, + name: "com.Tiercel.SessionManager.delegateQueue") + protectedState.write { + let session = URLSession(configuration: sessionConfiguration, + delegate: sessionDelegate, + delegateQueue: delegateQueue) + $0.session = session + $0.tasks.forEach { $0.session = session } + $0.shouldCreatSession = false + } + completion?() + } +} + + +// MARK: - download +extension SessionManager { + + + /// 开启一个下载任务 + /// + /// - Parameters: + /// - url: URLConvertible + /// - headers: headers + /// - fileName: 下载文件的文件名,如果传nil,则默认为url的md5加上文件扩展名 + /// - Returns: 如果url有效,则返回对应的task;如果url无效,则返回nil + @discardableResult + public func download(_ url: URLConvertible, + headers: [String: String]? = nil, + fileName: String? = nil, + onMainQueue: Bool = true, + handler: Handler? = nil) -> DownloadTask? { + do { + let validURL = try url.asURL() + var task: DownloadTask! + operationQueue.sync { + task = fetchTask(validURL) + if let task = task { + task.update(headers, newFileName: fileName) + } else { + task = DownloadTask(validURL, + headers: headers, + fileName: fileName, + cache: cache, + operationQueue: operationQueue) + task.manager = self + task.session = session + maintainTasks(with: .append(task)) + } + storeTasks() + start(task, onMainQueue: onMainQueue, handler: handler) + } + return task + } catch { + log(.error("create dowloadTask failed", error: error)) + return nil + } + + } + + + /// 批量开启多个下载任务, 所有任务都会并发下载 + /// + /// - Parameters: + /// - urls: [URLConvertible] + /// - headers: headers + /// - fileNames: 下载文件的文件名,如果传nil,则默认为url的md5加上文件扩展名 + /// - Returns: 返回url数组中有效url对应的task数组 + @discardableResult + public func multiDownload(_ urls: [URLConvertible], + headersArray: [[String: String]]? = nil, + fileNames: [String]? = nil, + onMainQueue: Bool = true, + handler: Handler? = nil) -> [DownloadTask] { + if let headersArray = headersArray, + headersArray.count != 0 && headersArray.count != urls.count { + log(.error("create multiple dowloadTasks failed", error: TiercelError.headersMatchFailed)) + return [DownloadTask]() + } + + if let fileNames = fileNames, + fileNames.count != 0 && fileNames.count != urls.count { + log(.error("create multiple dowloadTasks failed", error: TiercelError.fileNamesMatchFailed)) + return [DownloadTask]() + } + + var urlSet = Set() + var uniqueTasks = [DownloadTask]() + + operationQueue.sync { + for (index, url) in urls.enumerated() { + let fileName = fileNames?.safeObject(at: index) + let headers = headersArray?.safeObject(at: index) + + guard let validURL = try? url.asURL() else { + log(.error("create dowloadTask failed", error: TiercelError.invalidURL(url: url))) + continue + } + guard urlSet.insert(validURL).inserted else { + log(.error("create dowloadTask failed", error: TiercelError.duplicateURL(url: url))) + continue + } + + var task: DownloadTask! + task = fetchTask(validURL) + if let task = task { + task.update(headers, newFileName: fileName) + } else { + task = DownloadTask(validURL, + headers: headers, + fileName: fileName, + cache: cache, + operationQueue: operationQueue) + task.manager = self + task.session = session + maintainTasks(with: .append(task)) + } + uniqueTasks.append(task) + } + storeTasks() + Executer(onMainQueue: onMainQueue, handler: handler).execute(self) + operationQueue.async { + uniqueTasks.forEach { + if $0.status != .succeeded { + self._start($0) + } + } + } + } + return uniqueTasks + } +} + +// MARK: - single task control +extension SessionManager { + + public func fetchTask(_ url: URLConvertible) -> DownloadTask? { + do { + let validURL = try url.asURL() + return protectedState.read { $0.taskMapper[validURL.absoluteString] } + } catch { + log(.error("fetch task failed", error: TiercelError.invalidURL(url: url))) + return nil + } + } + + internal func mapTask(_ currentURL: URL) -> DownloadTask? { + protectedState.read { + let url = $0.urlMapper[currentURL] ?? currentURL + return $0.taskMapper[url.absoluteString] + } + } + + + + /// 开启任务 + /// 会检查存放下载完成的文件中是否存在跟fileName一样的文件 + /// 如果存在则不会开启下载,直接调用task的successHandler + public func start(_ url: URLConvertible, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + self._start(url, onMainQueue: onMainQueue, handler: handler) + } + } + + public func start(_ task: DownloadTask, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard let _ = self.fetchTask(task.url) else { + self.log(.error("can't start downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: task.url))) + return + } + self._start(task, onMainQueue: onMainQueue, handler: handler) + } + } + + private func _start(_ url: URLConvertible, onMainQueue: Bool = true, handler: Handler? = nil) { + guard let task = self.fetchTask(url) else { + log(.error("can't start downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: url))) + return + } + _start(task, onMainQueue: onMainQueue, handler: handler) + } + + private func _start(_ task: DownloadTask, onMainQueue: Bool = true, handler: Handler? = nil) { + task.controlExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + didStart() + if !shouldCreatSession { + task.download() + } else { + task.status = .suspended + if !restartTasks.contains(task) { + restartTasks.append(task) + } + } + } + + + /// 暂停任务,会触发sessionDelegate的完成回调 + public func suspend(_ url: URLConvertible, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard let task = self.fetchTask(url) else { + self.log(.error("can't suspend downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: url))) + return + } + task.suspend(onMainQueue: onMainQueue, handler: handler) + } + } + + public func suspend(_ task: DownloadTask, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard let _ = self.fetchTask(task.url) else { + self.log(.error("can't suspend downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: task.url))) + return + } + task.suspend(onMainQueue: onMainQueue, handler: handler) + } + } + + /// 取消任务 + /// 不会对已经完成的任务造成影响 + /// 其他状态的任务都可以被取消,被取消的任务会被移除 + /// 会删除还没有下载完成的缓存文件 + /// 会触发sessionDelegate的完成回调 + public func cancel(_ url: URLConvertible, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard let task = self.fetchTask(url) else { + self.log(.error("can't cancel downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: url))) + return + } + task.cancel(onMainQueue: onMainQueue, handler: handler) + } + } + + public func cancel(_ task: DownloadTask, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard let _ = self.fetchTask(task.url) else { + self.log(.error("can't cancel downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: task.url))) + return + } + task.cancel(onMainQueue: onMainQueue, handler: handler) + } + } + + + /// 移除任务 + /// 所有状态的任务都可以被移除 + /// 会删除还没有下载完成的缓存文件 + /// 可以选择是否删除下载完成的文件 + /// 会触发sessionDelegate的完成回调 + /// + /// - Parameters: + /// - url: URLConvertible + /// - completely: 是否删除下载完成的文件 + public func remove(_ url: URLConvertible, completely: Bool = false, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard let task = self.fetchTask(url) else { + self.log(.error("can't remove downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: url))) + return + } + task.remove(completely: completely, onMainQueue: onMainQueue, handler: handler) + } + } + + public func remove(_ task: DownloadTask, completely: Bool = false, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard let _ = self.fetchTask(task.url) else { + self.log(.error("can't remove downloadTask", error: TiercelError.fetchDownloadTaskFailed(url: task.url))) + return + } + task.remove(completely: completely, onMainQueue: onMainQueue, handler: handler) + } + } + + public func moveTask(at sourceIndex: Int, to destinationIndex: Int) { + operationQueue.sync { + let range = (0..? = nil) { + operationQueue.async { + self.tasks.forEach { task in + if task.status != .succeeded { + self._start(task) + } + } + Executer(onMainQueue: onMainQueue, handler: handler).execute(self) + } + } + + public func totalSuspend(onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard self.status == .running || self.status == .waiting else { return } + self.status = .willSuspend + self.controlExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + self.tasks.forEach { $0.suspend() } + } + } + + public func totalCancel(onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard self.status != .succeeded && self.status != .canceled else { return } + self.status = .willCancel + self.controlExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + self.tasks.forEach { $0.cancel() } + } + } + + public func totalRemove(completely: Bool = false, onMainQueue: Bool = true, handler: Handler? = nil) { + operationQueue.async { + guard self.status != .removed else { return } + self.status = .willRemove + self.controlExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + self.tasks.forEach { $0.remove(completely: completely) } + } + } + + public func tasksSort(by areInIncreasingOrder: (DownloadTask, DownloadTask) throws -> Bool) rethrows { + try operationQueue.sync { + try protectedState.write { + try $0.tasks.sort(by: areInIncreasingOrder) + } + } + } +} + + +// MARK: - status handle +extension SessionManager { + + internal func maintainTasks(with action: MaintainTasksAction) { + + switch action { + case let .append(task): + protectedState.write { state in + state.tasks.append(task) + state.taskMapper[task.url.absoluteString] = task + state.urlMapper[task.currentURL] = task.url + } + case let .remove(task): + protectedState.write { state in + if state.status == .willRemove { + state.taskMapper.removeValue(forKey: task.url.absoluteString) + state.urlMapper.removeValue(forKey: task.currentURL) + if state.taskMapper.values.isEmpty { + state.tasks.removeAll() + state.succeededTasks.removeAll() + } + } else if state.status == .willCancel { + state.taskMapper.removeValue(forKey: task.url.absoluteString) + state.urlMapper.removeValue(forKey: task.currentURL) + if state.taskMapper.values.count == state.succeededTasks.count { + state.tasks = state.succeededTasks + } + } else { + state.taskMapper.removeValue(forKey: task.url.absoluteString) + state.urlMapper.removeValue(forKey: task.currentURL) + state.tasks.removeAll { + $0.url.absoluteString == task.url.absoluteString + } + if task.status == .removed { + state.succeededTasks.removeAll { + $0.url.absoluteString == task.url.absoluteString + } + } + } + } + case let .succeeded(task): + succeededTasks.append(task) + case let .appendRunningTasks(task): + protectedState.write { state in + state.runningTasks.append(task) + } + case let .removeRunningTasks(task): + protectedState.write { state in + state.runningTasks.removeAll { + $0.url.absoluteString == task.url.absoluteString + } + } + } + } + + internal func updateUrlMapper(with task: DownloadTask) { + protectedState.write { $0.urlMapper[task.currentURL] = task.url } + } + + private func restoreStatus() { + if self.tasks.isEmpty { + return + } + session?.getTasksWithCompletionHandler { [weak self] (dataTasks, uploadTasks, downloadTasks) in + guard let self = self else { return } + downloadTasks.forEach { downloadTask in + if downloadTask.state == .running, + let currentURL = downloadTask.currentRequest?.url, + let task = self.mapTask(currentURL) { + self.didStart() + self.maintainTasks(with: .appendRunningTasks(task)) + task.status = .running + task.sessionTask = downloadTask + } + } + self.storeTasks() + // 处理mananger状态 + if !self.shouldComplete() { + self.shouldSuspend() + } + } + } + + + private func shouldComplete() -> Bool { + + let isSucceeded = self.tasks.allSatisfy { $0.status == .succeeded } + let isCompleted = isSucceeded ? isSucceeded : + self.tasks.allSatisfy { $0.status == .succeeded || $0.status == .failed } + guard isCompleted else { return false } + + if status == .succeeded || status == .failed { + return true + } + timeRemaining = 0 + progressExecuter?.execute(self) + status = isSucceeded ? .succeeded : .failed + executeCompletion(isSucceeded) + return true + } + + + + private func shouldSuspend() { + let isSuspended = tasks.allSatisfy { $0.status == .suspended || $0.status == .succeeded || $0.status == .failed } + + if isSuspended { + if status == .suspended { + return + } + status = .suspended + executeControl() + executeCompletion(false) + if shouldCreatSession { + session?.invalidateAndCancel() + session = nil + } + } + } + + internal func didStart() { + if status != .running { + createTimer() + status = .running + progressExecuter?.execute(self) + } + } + + internal func updateProgress() { + if isControlNetworkActivityIndicator { + DispatchQueue.tr.executeOnMain { + UIApplication.shared.isNetworkActivityIndicatorVisible = true + } + } + progressExecuter?.execute(self) + NotificationCenter.default.postNotification(name: SessionManager.runningNotification, sessionManager: self) + } + + internal func didCancelOrRemove(_ task: DownloadTask) { + maintainTasks(with: .remove(task)) + + // 处理使用单个任务操作移除最后一个task时,manager状态 + if tasks.isEmpty { + if task.status == .canceled { + status = .willCancel + } + if task.status == .removed { + status = .willRemove + } + } + } + + internal func storeTasks() { + cache.storeTasks(tasks) + } + + internal func determineStatus(fromRunningTask: Bool) { + if isControlNetworkActivityIndicator { + DispatchQueue.tr.executeOnMain { + UIApplication.shared.isNetworkActivityIndicatorVisible = false + } + } + + // removed + if status == .willRemove { + if tasks.isEmpty { + status = .removed + executeControl() + ending(false) + } + return + } + + // canceled + if status == .willCancel { + let succeededTasksCount = protectedState.wrappedValue.taskMapper.values.count + if tasks.count == succeededTasksCount { + status = .canceled + executeControl() + ending(false) + return + } + return + } + + // completed + let isCompleted = tasks.allSatisfy { $0.status == .succeeded || $0.status == .failed } + + if isCompleted { + if status == .succeeded || status == .failed { + storeTasks() + return + } + timeRemaining = 0 + progressExecuter?.execute(self) + let isSucceeded = tasks.allSatisfy { $0.status == .succeeded } + status = isSucceeded ? .succeeded : .failed + ending(isSucceeded) + return + } + + // suspended + let isSuspended = tasks.allSatisfy { $0.status == .suspended || + $0.status == .succeeded || + $0.status == .failed } + + if isSuspended { + if status == .suspended { + storeTasks() + return + } + status = .suspended + if shouldCreatSession { + session?.invalidateAndCancel() + session = nil + } else { + executeControl() + ending(false) + } + return + } + + if status == .willSuspend { + return + } + + storeTasks() + + if fromRunningTask { + // next task + operationQueue.async { + self.startNextTask() + } + } + } + + private func ending(_ isSucceeded: Bool) { + executeCompletion(isSucceeded) + storeTasks() + invalidateTimer() + } + + + private func startNextTask() { + guard let waitingTask = tasks.first (where: { $0.status == .waiting }) else { return } + waitingTask.download() + } +} + +// MARK: - info +extension SessionManager { + + static let refreshInterval: Double = 1 + + private func createTimer() { + if timer == nil { + timer = DispatchSource.makeTimerSource(flags: .strict, queue: operationQueue) + timer?.schedule(deadline: .now(), repeating: Self.refreshInterval) + timer?.setEventHandler(handler: { [weak self] in + guard let self = self else { return } + self.updateSpeedAndTimeRemaining() + }) + timer?.resume() + } + } + + private func invalidateTimer() { + timer?.cancel() + timer = nil + } + + internal func updateSpeedAndTimeRemaining() { + let speed = runningTasks.reduce(Int64(0), { + $1.updateSpeedAndTimeRemaining() + return $0 + $1.speed + }) + updateTimeRemaining(speed) + } + + private func updateTimeRemaining(_ speed: Int64) { + var timeRemaining: Double + if speed != 0 { + timeRemaining = (Double(progress.totalUnitCount) - Double(progress.completedUnitCount)) / Double(speed) + if timeRemaining >= 0.8 && timeRemaining < 1 { + timeRemaining += 1 + } + } else { + timeRemaining = 0 + } + protectedState.write { + $0.speed = speed + $0.timeRemaining = Int64(timeRemaining) + } + } + + + + internal func log(_ type: LogType) { + logger.log(type) + } +} + +// MARK: - closure +extension SessionManager { + @discardableResult + public func progress(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + progressExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + return self + } + + @discardableResult + public func success(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + successExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if status == .succeeded && completionExecuter == nil{ + operationQueue.async { + self.successExecuter?.execute(self) + } + } + return self + } + + @discardableResult + public func failure(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + failureExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if completionExecuter == nil && + (status == .suspended || + status == .canceled || + status == .removed || + status == .failed) { + operationQueue.async { + self.failureExecuter?.execute(self) + } + } + return self + } + + @discardableResult + public func completion(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + completionExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if status == .suspended || + status == .canceled || + status == .removed || + status == .succeeded || + status == .failed { + operationQueue.async { + self.completionExecuter?.execute(self) + } + } + return self + } + + private func executeCompletion(_ isSucceeded: Bool) { + if let completionExecuter = completionExecuter { + completionExecuter.execute(self) + } else if isSucceeded { + successExecuter?.execute(self) + } else { + failureExecuter?.execute(self) + } + NotificationCenter.default.postNotification(name: SessionManager.didCompleteNotification, sessionManager: self) + } + + private func executeControl() { + controlExecuter?.execute(self) + controlExecuter = nil + } +} + + +// MARK: - call back +extension SessionManager { + internal func didBecomeInvalidation(withError error: Error?) { + createSession { [weak self] in + guard let self = self else { return } + self.restartTasks.forEach { self._start($0) } + self.restartTasks.removeAll() + } + } + + internal func didFinishEvents(forBackgroundURLSession session: URLSession) { + DispatchQueue.tr.executeOnMain { + self.completionHandler?() + self.completionHandler = nil + } + } + +} + + diff --git a/Pods/Tiercel/Sources/General/Task.swift b/Pods/Tiercel/Sources/General/Task.swift new file mode 100644 index 0000000..d3dce65 --- /dev/null +++ b/Pods/Tiercel/Sources/General/Task.swift @@ -0,0 +1,363 @@ +// +// Task.swift +// Tiercel +// +// Created by Daniels on 2018/3/16. +// Copyright © 2018 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension Task { + public enum Validation: Int { + case unkown + case correct + case incorrect + } +} + +public class Task: NSObject, Codable { + + private enum CodingKeys: CodingKey { + case url + case currentURL + case fileName + case headers + case startDate + case endDate + case totalBytes + case completedBytes + case verificationCode + case status + case verificationType + case validation + case error + } + + enum CompletionType { + case local + case network(task: URLSessionTask, error: Error?) + } + + enum InterruptType { + case manual(_ fromRunningTask: Bool) + case error(_ error: Error) + case statusCode(_ statusCode: Int) + } + + public internal(set) weak var manager: SessionManager? + + internal var cache: Cache + + internal var operationQueue: DispatchQueue + + public let url: URL + + public let progress: Progress = Progress() + + internal struct State { + var session: URLSession? + var headers: [String: String]? + var verificationCode: String? + var verificationType: FileChecksumHelper.VerificationType = .md5 + var isRemoveCompletely: Bool = false + var status: Status = .waiting + var validation: Validation = .unkown + var currentURL: URL + var startDate: Double = 0 + var endDate: Double = 0 + var speed: Int64 = 0 + var fileName: String + var timeRemaining: Int64 = 0 + var error: Error? + + var progressExecuter: Executer? + var successExecuter: Executer? + var failureExecuter: Executer? + var controlExecuter: Executer? + var completionExecuter: Executer? + var validateExecuter: Executer? + } + + + internal let protectedState: Protected + + internal var session: URLSession? { + get { protectedState.wrappedValue.session } + set { protectedState.write { $0.session = newValue } } + } + + internal var headers: [String: String]? { + get { protectedState.wrappedValue.headers } + set { protectedState.write { $0.headers = newValue } } + } + + internal var verificationCode: String? { + get { protectedState.wrappedValue.verificationCode } + set { protectedState.write { $0.verificationCode = newValue } } + } + + internal var verificationType: FileChecksumHelper.VerificationType { + get { protectedState.wrappedValue.verificationType } + set { protectedState.write { $0.verificationType = newValue } } + } + + internal var isRemoveCompletely: Bool { + get { protectedState.wrappedValue.isRemoveCompletely } + set { protectedState.write { $0.isRemoveCompletely = newValue } } + } + + public internal(set) var status: Status { + get { protectedState.wrappedValue.status } + set { + protectedState.write { $0.status = newValue } + if newValue == .willSuspend || newValue == .willCancel || newValue == .willRemove { + return + } + if self is DownloadTask { + manager?.log(.downloadTask(newValue.rawValue, task: self as! DownloadTask)) + } + } + } + + public internal(set) var validation: Validation { + get { protectedState.wrappedValue.validation } + set { protectedState.write { $0.validation = newValue } } + } + + internal var currentURL: URL { + get { protectedState.wrappedValue.currentURL } + set { protectedState.write { $0.currentURL = newValue } } + } + + + public internal(set) var startDate: Double { + get { protectedState.wrappedValue.startDate } + set { protectedState.write { $0.startDate = newValue } } + } + + public var startDateString: String { + startDate.tr.convertTimeToDateString() + } + + public internal(set) var endDate: Double { + get { protectedState.wrappedValue.endDate } + set { protectedState.write { $0.endDate = newValue } } + } + + public var endDateString: String { + endDate.tr.convertTimeToDateString() + } + + + public internal(set) var speed: Int64 { + get { protectedState.wrappedValue.speed } + set { protectedState.write { $0.speed = newValue } } + } + + public var speedString: String { + speed.tr.convertSpeedToString() + } + + /// 默认为url的md5加上文件扩展名 + public internal(set) var fileName: String { + get { protectedState.wrappedValue.fileName } + set { protectedState.write { $0.fileName = newValue } } + } + + public internal(set) var timeRemaining: Int64 { + get { protectedState.wrappedValue.timeRemaining } + set { protectedState.write { $0.timeRemaining = newValue } } + } + + public var timeRemainingString: String { + timeRemaining.tr.convertTimeToString() + } + + public internal(set) var error: Error? { + get { protectedState.wrappedValue.error } + set { protectedState.write { $0.error = newValue } } + } + + + internal var progressExecuter: Executer? { + get { protectedState.wrappedValue.progressExecuter } + set { protectedState.write { $0.progressExecuter = newValue } } + } + + internal var successExecuter: Executer? { + get { protectedState.wrappedValue.successExecuter } + set { protectedState.write { $0.successExecuter = newValue } } + } + + internal var failureExecuter: Executer? { + get { protectedState.wrappedValue.failureExecuter } + set { protectedState.write { $0.failureExecuter = newValue } } + } + + internal var completionExecuter: Executer? { + get { protectedState.wrappedValue.completionExecuter } + set { protectedState.write { $0.completionExecuter = newValue } } + } + + internal var controlExecuter: Executer? { + get { protectedState.wrappedValue.controlExecuter } + set { protectedState.write { $0.controlExecuter = newValue } } + } + + internal var validateExecuter: Executer? { + get { protectedState.wrappedValue.validateExecuter } + set { protectedState.write { $0.validateExecuter = newValue } } + } + + + + internal init(_ url: URL, + headers: [String: String]? = nil, + cache: Cache, + operationQueue:DispatchQueue) { + self.cache = cache + self.url = url + self.operationQueue = operationQueue + protectedState = Protected(State(currentURL: url, fileName: url.tr.fileName)) + super.init() + self.headers = headers + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(url, forKey: .url) + try container.encode(currentURL, forKey: .currentURL) + try container.encode(fileName, forKey: .fileName) + try container.encodeIfPresent(headers, forKey: .headers) + try container.encode(startDate, forKey: .startDate) + try container.encode(endDate, forKey: .endDate) + try container.encode(progress.totalUnitCount, forKey: .totalBytes) + try container.encode(progress.completedUnitCount, forKey: .completedBytes) + try container.encode(status.rawValue, forKey: .status) + try container.encodeIfPresent(verificationCode, forKey: .verificationCode) + try container.encode(verificationType.rawValue, forKey: .verificationType) + try container.encode(validation.rawValue, forKey: .validation) + if let error = error { + let errorData: Data + if #available(iOS 11.0, *) { + errorData = try NSKeyedArchiver.archivedData(withRootObject: (error as NSError), requiringSecureCoding: true) + } else { + errorData = NSKeyedArchiver.archivedData(withRootObject: (error as NSError)) + } + try container.encode(errorData, forKey: .error) + } + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + url = try container.decode(URL.self, forKey: .url) + let currentURL = try container.decode(URL.self, forKey: .currentURL) + let fileName = try container.decode(String.self, forKey: .fileName) + protectedState = Protected(State(currentURL: currentURL, fileName: fileName)) + cache = decoder.userInfo[.cache] as? Cache ?? Cache("default") + operationQueue = decoder.userInfo[.operationQueue] as? DispatchQueue ?? DispatchQueue(label: "com.Tiercel.SessionManager.operationQueue") + super.init() + + progress.totalUnitCount = try container.decode(Int64.self, forKey: .totalBytes) + progress.completedUnitCount = try container.decode(Int64.self, forKey: .completedBytes) + + let statusString = try container.decode(String.self, forKey: .status) + let verificationTypeInt = try container.decode(Int.self, forKey: .verificationType) + let validationType = try container.decode(Int.self, forKey: .validation) + + try protectedState.write { + $0.headers = try container.decodeIfPresent([String: String].self, forKey: .headers) + $0.startDate = try container.decode(Double.self, forKey: .startDate) + $0.endDate = try container.decode(Double.self, forKey: .endDate) + $0.verificationCode = try container.decodeIfPresent(String.self, forKey: .verificationCode) + $0.status = Status(rawValue: statusString)! + $0.verificationType = FileChecksumHelper.VerificationType(rawValue: verificationTypeInt)! + $0.validation = Validation(rawValue: validationType)! + if let errorData = try container.decodeIfPresent(Data.self, forKey: .error) { + if #available(iOS 11.0, *) { + $0.error = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSError.self, from: errorData) + } else { + $0.error = NSKeyedUnarchiver.unarchiveObject(with: errorData) as? NSError + } + } + } + } + + internal func execute(_ Executer: Executer?) { + + } + +} + + +extension Task { + @discardableResult + public func progress(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + progressExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + return self + } + + @discardableResult + public func success(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + successExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if status == .succeeded && completionExecuter == nil{ + operationQueue.async { + self.execute(self.successExecuter) + } + } + return self + + } + + @discardableResult + public func failure(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + failureExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if completionExecuter == nil && + (status == .suspended || + status == .canceled || + status == .removed || + status == .failed) { + operationQueue.async { + self.execute(self.failureExecuter) + } + } + return self + } + + @discardableResult + public func completion(onMainQueue: Bool = true, handler: @escaping Handler) -> Self { + completionExecuter = Executer(onMainQueue: onMainQueue, handler: handler) + if status == .suspended || + status == .canceled || + status == .removed || + status == .succeeded || + status == .failed { + operationQueue.async { + self.execute(self.completionExecuter) + } + } + return self + } + +} + + diff --git a/Pods/Tiercel/Sources/General/TiercelError.swift b/Pods/Tiercel/Sources/General/TiercelError.swift new file mode 100644 index 0000000..18ee3fa --- /dev/null +++ b/Pods/Tiercel/Sources/General/TiercelError.swift @@ -0,0 +1,127 @@ +// +// TiercelError.swift +// Tiercel +// +// Created by Daniels on 2019/5/14. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public enum TiercelError: Error { + + public enum CacheErrorReason { + case cannotCreateDirectory(path: String, error: Error) + case cannotRemoveItem(path: String, error: Error) + case cannotCopyItem(atPath: String, toPath: String, error: Error) + case cannotMoveItem(atPath: String, toPath: String, error: Error) + case cannotRetrieveAllTasks(path: String, error: Error) + case cannotEncodeTasks(path: String, error: Error) + case fileDoesnotExist(path: String) + case readDataFailed(path: String) + } + + case unknown + case invalidURL(url: URLConvertible) + case duplicateURL(url: URLConvertible) + case indexOutOfRange + case fetchDownloadTaskFailed(url: URLConvertible) + case headersMatchFailed + case fileNamesMatchFailed + case unacceptableStatusCode(code: Int) + case cacheError(reason: CacheErrorReason) +} + +extension TiercelError: LocalizedError { + public var errorDescription: String? { + switch self { + case .unknown: + return "unkown error" + case let .invalidURL(url): + return "URL is not valid: \(url)" + case let .duplicateURL(url): + return "URL is duplicate: \(url)" + case .indexOutOfRange: + return "index out of range" + case let .fetchDownloadTaskFailed(url): + return "did not find downloadTask in sessionManager: \(url)" + case .headersMatchFailed: + return "HeaderArray.count != urls.count" + case .fileNamesMatchFailed: + return "FileNames.count != urls.count" + case let .unacceptableStatusCode(code): + return "Response status code was unacceptable: \(code)" + case let .cacheError(reason): + return reason.errorDescription + } + } +} + +extension TiercelError: CustomNSError { + + public static let errorDomain: String = "com.Daniels.Tiercel.Error" + + public var errorCode: Int { + if case .unacceptableStatusCode = self { + return 1001 + } else { + return -1 + } + } + + public var errorUserInfo: [String: Any] { + if let errorDescription = errorDescription { + return [NSLocalizedDescriptionKey: errorDescription] + } else { + return [String: Any]() + } + + } +} + +extension TiercelError.CacheErrorReason { + + public var errorDescription: String? { + switch self { + case let .cannotCreateDirectory(path, error): + return "can not create directory, path: \(path), underlying: \(error)" + case let .cannotRemoveItem(path, error): + return "can not remove item, path: \(path), underlying: \(error)" + case let .cannotCopyItem(atPath, toPath, error): + return "can not copy item, atPath: \(atPath), toPath: \(toPath), underlying: \(error)" + case let .cannotMoveItem(atPath, toPath, error): + return "can not move item atPath: \(atPath), toPath: \(toPath), underlying: \(error)" + case let .cannotRetrieveAllTasks(path, error): + return "can not retrieve all tasks, path: \(path), underlying: \(error)" + case let .cannotEncodeTasks(path, error): + return "can not encode tasks, path: \(path), underlying: \(error)" + case let .fileDoesnotExist(path): + return "file does not exist, path: \(path)" + case let .readDataFailed(path): + return "read data failed, path: \(path)" + } + } + + +} + + + diff --git a/Pods/Tiercel/Sources/General/URLConvertible.swift b/Pods/Tiercel/Sources/General/URLConvertible.swift new file mode 100644 index 0000000..2efea0d --- /dev/null +++ b/Pods/Tiercel/Sources/General/URLConvertible.swift @@ -0,0 +1,55 @@ +// +// URLConvertible.swift +// Tiercel +// +// Created by Daniels on 2019/5/14. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +public protocol URLConvertible { + + func asURL() throws -> URL +} + +extension String: URLConvertible { + + public func asURL() throws -> URL { + guard let url = URL(string: self) else { throw TiercelError.invalidURL(url: self) } + + return url + } +} + +extension URL: URLConvertible { + + public func asURL() throws -> URL { return self } +} + +extension URLComponents: URLConvertible { + + public func asURL() throws -> URL { + guard let url = url else { throw TiercelError.invalidURL(url: self) } + + return url + } +} diff --git a/Pods/Tiercel/Sources/Utility/FileChecksumHelper.swift b/Pods/Tiercel/Sources/Utility/FileChecksumHelper.swift new file mode 100644 index 0000000..bccfb15 --- /dev/null +++ b/Pods/Tiercel/Sources/Utility/FileChecksumHelper.swift @@ -0,0 +1,109 @@ +// +// FileChecksumHelper.swift +// Tiercel +// +// Created by Daniels on 2019/1/22. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + + +public enum FileChecksumHelper { + + public enum VerificationType : Int { + case md5 + case sha1 + case sha256 + case sha512 + } + + public enum FileVerificationError: Error { + case codeEmpty + case codeMismatch(code: String) + case fileDoesnotExist(path: String) + case readDataFailed(path: String) + } + + private static let ioQueue: DispatchQueue = DispatchQueue(label: "com.Tiercel.FileChecksumHelper.ioQueue", + attributes: .concurrent) + + + public static func validateFile(_ filePath: String, + code: String, + type: VerificationType, + completion: @escaping (Result) -> ()) { + if code.isEmpty { + completion(.failure(FileVerificationError.codeEmpty)) + return + } + ioQueue.async { + guard FileManager.default.fileExists(atPath: filePath) else { + completion(.failure(FileVerificationError.fileDoesnotExist(path: filePath))) + return + } + let url = URL(fileURLWithPath: filePath) + + do { + let data = try Data(contentsOf: url, options: .mappedIfSafe) + var string: String + switch type { + case .md5: + string = data.tr.md5 + case .sha1: + string = data.tr.sha1 + case .sha256: + string = data.tr.sha256 + case .sha512: + string = data.tr.sha512 + } + let isCorrect = string.lowercased() == code.lowercased() + if isCorrect { + completion(.success(true)) + } else { + completion(.failure(FileVerificationError.codeMismatch(code: code))) + } + } catch { + completion(.failure(FileVerificationError.readDataFailed(path: filePath))) + } + } + } +} + + + +extension FileChecksumHelper.FileVerificationError: LocalizedError { + public var errorDescription: String? { + switch self { + case .codeEmpty: + return "verification code is empty" + case let .codeMismatch(code): + return "verification code mismatch, code: \(code)" + case let .fileDoesnotExist(path): + return "file does not exist, path: \(path)" + case let .readDataFailed(path): + return "read data failed, path: \(path)" + } + } + +} + + diff --git a/Pods/Tiercel/Sources/Utility/ResumeDataHelper.swift b/Pods/Tiercel/Sources/Utility/ResumeDataHelper.swift new file mode 100644 index 0000000..581e2ca --- /dev/null +++ b/Pods/Tiercel/Sources/Utility/ResumeDataHelper.swift @@ -0,0 +1,198 @@ +// +// ResumeDataHelper.swift +// Tiercel +// +// Created by Daniels on 2019/1/7. +// Copyright © 2019 Daniels. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + + +internal enum ResumeDataHelper { + + static let infoVersionKey = "NSURLSessionResumeInfoVersion" + static let currentRequestKey = "NSURLSessionResumeCurrentRequest" + static let originalRequestKey = "NSURLSessionResumeOriginalRequest" + static let resumeByteRangeKey = "NSURLSessionResumeByteRange" + static let infoTempFileNameKey = "NSURLSessionResumeInfoTempFileName" + static let infoLocalPathKey = "NSURLSessionResumeInfoLocalPath" + static let bytesReceivedKey = "NSURLSessionResumeBytesReceived" + static let archiveRootObjectKey = "NSKeyedArchiveRootObjectKey" + + internal static func handleResumeData(_ data: Data) -> Data? { + if #available(iOS 11.3, *) { + return data + } else if #available(iOS 11.0, *) { + // 修复 11.0 - 11.2 bug + return deleteResumeByteRange(data) + } else if #available(iOS 10.2, *) { + return data + } else if #available(iOS 10.0, *) { + // 修复 10.0 - 10.1 bug + return correctResumeData(data) + } else { + return data + } + } + + + /// 修复 11.0 - 11.2 resumeData bug + /// + /// - Parameter data: + /// - Returns: + private static func deleteResumeByteRange(_ data: Data) -> Data? { + guard let resumeDictionary = getResumeDictionary(data) else { return nil } + resumeDictionary.removeObject(forKey: resumeByteRangeKey) + return try? PropertyListSerialization.data(fromPropertyList: resumeDictionary, + format: PropertyListSerialization.PropertyListFormat.xml, + options: PropertyListSerialization.WriteOptions()) + } + + + /// 修复 10.0 - 10.1 resumeData bug + /// + /// - Parameter data: + /// - Returns: + private static func correctResumeData(_ data: Data) -> Data? { + guard let resumeDictionary = getResumeDictionary(data) else { return nil } + + if let currentRequest = resumeDictionary[currentRequestKey] as? Data { + resumeDictionary[currentRequestKey] = correct(with: currentRequest) + } + if let originalRequest = resumeDictionary[originalRequestKey] as? Data { + resumeDictionary[originalRequestKey] = correct(with: originalRequest) + } + + return try? PropertyListSerialization.data(fromPropertyList: resumeDictionary, + format: PropertyListSerialization.PropertyListFormat.xml, + options: PropertyListSerialization.WriteOptions()) + } + + + /// 把resumeData解析成字典 + /// + /// - Parameter data: + /// - Returns: + internal static func getResumeDictionary(_ data: Data) -> NSMutableDictionary? { + // In beta versions, resumeData is NSKeyedArchive encoded instead of plist + var object: NSDictionary? + if #available(OSX 10.11, iOS 9.0, *) { + let keyedUnarchiver = NSKeyedUnarchiver(forReadingWith: data) + + do { + object = try keyedUnarchiver.decodeTopLevelObject(of: NSDictionary.self, forKey: archiveRootObjectKey) + if object == nil { + object = try keyedUnarchiver.decodeTopLevelObject(of: NSDictionary.self, forKey: NSKeyedArchiveRootObjectKey) + } + } catch {} + keyedUnarchiver.finishDecoding() + } + + if object == nil { + do { + object = try PropertyListSerialization.propertyList(from: data, + options: .mutableContainersAndLeaves, + format: nil) as? NSDictionary + } catch {} + } + + if let resumeDictionary = object as? NSMutableDictionary { + return resumeDictionary + } + + guard let resumeDictionary = object else { return nil } + return NSMutableDictionary(dictionary: resumeDictionary) + + } + + internal static func getTmpFileName(_ data: Data) -> String? { + guard let resumeDictionary = ResumeDataHelper.getResumeDictionary(data), + let version = resumeDictionary[infoVersionKey] as? Int + else { return nil } + if version > 1 { + return resumeDictionary[infoTempFileNameKey] as? String + } else { + guard let path = resumeDictionary[infoLocalPathKey] as? String else { return nil } + let url = URL(fileURLWithPath: path) + return url.lastPathComponent + } + + } + + + /// 修复resumeData中的当前请求数据和原始请求数据 + /// + /// - Parameter data: + /// - Returns: + private static func correct(with data: Data) -> Data? { + if NSKeyedUnarchiver.unarchiveObject(with: data) != nil { + return data + } + guard let resumeDictionary = try? PropertyListSerialization.propertyList(from: data, + options: .mutableContainersAndLeaves, + format: nil) as? NSMutableDictionary + else { return nil } + // Rectify weird __nsurlrequest_proto_props objects to $number pattern + var k = 0 + while ((resumeDictionary["$objects"] as? NSArray)?[1] as? NSDictionary)?.object(forKey: "$\(k)") != nil { + k += 1 + } + var i = 0 + while ((resumeDictionary["$objects"] as? NSArray)?[1] as? NSDictionary)?.object(forKey: "__nsurlrequest_proto_prop_obj_\(i)") != nil { + let arr = resumeDictionary["$objects"] as? NSMutableArray + if let dic = arr?[1] as? NSMutableDictionary, let obj = dic["__nsurlrequest_proto_prop_obj_\(i)"] { + dic.setObject(obj, forKey: "$\(i + k)" as NSString) + dic.removeObject(forKey: "__nsurlrequest_proto_prop_obj_\(i)") + arr?[1] = dic + resumeDictionary["$objects"] = arr + } + i += 1 + } + if ((resumeDictionary["$objects"] as? NSArray)?[1] as? NSDictionary)?.object(forKey: "__nsurlrequest_proto_props") != nil { + let arr = resumeDictionary["$objects"] as? NSMutableArray + if let dic = arr?[1] as? NSMutableDictionary, let obj = dic["__nsurlrequest_proto_props"] { + dic.setObject(obj, forKey: "$\(i + k)" as NSString) + dic.removeObject(forKey: "__nsurlrequest_proto_props") + arr?[1] = dic + resumeDictionary["$objects"] = arr + } + } + + if let obj = (resumeDictionary["$top"] as? NSMutableDictionary)?.object(forKey: archiveRootObjectKey) as AnyObject? { + (resumeDictionary["$top"] as? NSMutableDictionary)?.setObject(obj, forKey: NSKeyedArchiveRootObjectKey as NSString) + (resumeDictionary["$top"] as? NSMutableDictionary)?.removeObject(forKey: archiveRootObjectKey) + } + // Reencode archived object + return try? PropertyListSerialization.data(fromPropertyList: resumeDictionary, + format: PropertyListSerialization.PropertyListFormat.binary, + options: PropertyListSerialization.WriteOptions()) + } + +} + + + + + + + +