Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3930096357 | |||
| 9e8ebdfca1 | |||
| 31f4745bae | |||
| 4a8af1bce3 | |||
| 8e6b72613d | |||
| ae760c4cac | |||
| b3856e0a7a | |||
| 757e5f476c | |||
| 1f5eae407d | |||
| 29660371c9 | |||
| c2530a0193 | |||
| 6804cf5163 | |||
| 6ea2e082b0 | |||
| b6b4599ef5 | |||
| 3ccf9ed20e | |||
| d6eb187788 | |||
| 1febb782d8 | |||
| c4c6f42ac0 | |||
| d8b4466629 | |||
| cb9dec49b2 | |||
| 7e46a91e73 | |||
| 8ca4a873a5 | |||
| 3f54de70bc | |||
| cae549a401 | |||
| 85fecef45e | |||
| e2375f7a82 | |||
| 3c8402503b | |||
| 4c46137498 | |||
| a4e023d75f | |||
| fe549a522a | |||
| 6cc0638a70 | |||
| 34c1f493ae | |||
| 30750a0c81 | |||
| e57cf9d2e5 | |||
| 0eeedc6467 | |||
| e96cbf6337 | |||
| debc1c519f | |||
| 09bec023a9 | |||
| 44e022389c | |||
| 05ca97b8eb | |||
| f6ff2b4cc0 | |||
| 9e3f5c0291 | |||
| 8ba40ca45f | |||
| 9919bde9fe | |||
| 3ba62d8657 | |||
| 0f283b171f | |||
| f6b5e30e85 | |||
| 04296fa681 | |||
| 252ed947d2 | |||
| b5fdb5c54e | |||
| 1b4c0b0d3b | |||
| e0aa2a09a9 | |||
| ab0eb4f8eb | |||
| 99e7c65bbc | |||
| 9072259631 | |||
| eb9af1007a | |||
| 69d3a9c0c0 | |||
| 43821c68a9 | |||
| 610ff4c7f3 | |||
| 1fc533214f | |||
| 71f22c3e25 | |||
| 8a5e6d18cc | |||
| a7f53bfec9 | |||
| 7b10f0476b | |||
| 618da75339 | |||
| 0694f5d8a0 | |||
| 61268b45eb | |||
| 962e64fe24 | |||
| 0f9b2656a1 | |||
| 5e871fc7e2 | |||
| fdf8fc9482 | |||
| a9eb713964 | |||
| a0efa5f408 | |||
| 74cafd4c42 | |||
| 53780ac03e | |||
| ba438c8ede | |||
| 06cb6577c1 | |||
| 5a0f379275 | |||
| 11e793f963 | |||
| d094067ac7 | |||
| 4bacd5f1ff | |||
| 73089bbe8b | |||
| b4f919e8f4 | |||
| 7e33789644 | |||
| 843ba9f450 | |||
| 4305110867 | |||
| cd43ecc6f9 | |||
| 274377af3f | |||
| ef41885eaf | |||
| 21da2da43b | |||
| 336d5586bf | |||
| e917867220 | |||
| 7897a79a79 | |||
| 43824a9700 | |||
| 460af7ab1e | |||
| 670bf0e09e | |||
| 238c02db12 | |||
| 20190152eb | |||
| 9e6683674b | |||
| b27332aafb | |||
| 18688ab766 | |||
| 6835738754 | |||
| 9f05915993 | |||
| 2f9a5481ff | |||
| 079f8c8ce1 | |||
| 720b4739b4 | |||
| 2b150b5652 | |||
| 4adc84aaf3 | |||
| 76f2d22e7a | |||
| 3c409200ee | |||
| b44777fcd7 | |||
| d682fd9468 | |||
| 10e6c46c18 | |||
| 89715d9d38 | |||
| 99bd43769c | |||
| ce5e5e886f | |||
| 521141ba0d | |||
| d3d354d5bd | |||
| 664c56b79c | |||
| 7bb83e87e0 | |||
| 6cad96b4f8 | |||
| 50a58c2306 | |||
| 2dbaf3d4dc | |||
| 851d8704d9 | |||
| 7c4dc27868 |
+1
-1
@@ -1 +1 @@
|
||||
4.0
|
||||
4.2
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
# references:
|
||||
# * http://www.objc.io/issue-6/travis-ci.html
|
||||
# * https://github.com/supermarin/xcpretty#usage
|
||||
|
||||
osx_image: xcode9.4
|
||||
language: swift
|
||||
cache: cocoapods
|
||||
podfile: Example/Podfile
|
||||
before_install:
|
||||
- gem install cocoapods # Since Travis is not always on latest version
|
||||
- pod install --project-directory=Example --repo-update
|
||||
script:
|
||||
- set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/SwiftAudio.xcworkspace -scheme SwiftAudio-Example -sdk iphonesimulator11.4 -destination "OS=11.4,name=iPhone X" | xcpretty
|
||||
- pod lib lint
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash) -J 'SwiftAudio'
|
||||
+96
-42
@@ -12,6 +12,19 @@
|
||||
0262F9E0C329E749D1B3480EBF568E48 /* Quick-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 25DBE2FD6322DB92AF73F714DBCDAC39 /* Quick-dummy.m */; };
|
||||
03D7EA5C17FCEB40D10C3C73F9472F4E /* NimbleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194F331624F24ECBAEF52731D29FAD3E /* NimbleEnvironment.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
04AA9A137A0D38056D1081B4B304CFB6 /* Predicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9753520784E15A0CF31F3233F37520 /* Predicate.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
074B0D6322289268001A45A9 /* NowPlayingInfoControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D6222289268001A45A9 /* NowPlayingInfoControllerProtocol.swift */; };
|
||||
074B0D652228929B001A45A9 /* NowPlayingInfoKeyValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D642228929B001A45A9 /* NowPlayingInfoKeyValue.swift */; };
|
||||
074B0D69222C23BB001A45A9 /* NowPlayingInfoCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D68222C23BB001A45A9 /* NowPlayingInfoCenter.swift */; };
|
||||
075131AD2182FF1600D3BFB9 /* AVPlayerWrapperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075131AC2182FF1600D3BFB9 /* AVPlayerWrapperProtocol.swift */; };
|
||||
075131AF21830D9500D3BFB9 /* AVPlayerWrapperDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075131AE21830D9500D3BFB9 /* AVPlayerWrapperDelegate.swift */; };
|
||||
075131B9218322E000D3BFB9 /* MediaItemProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075131B3218322E000D3BFB9 /* MediaItemProperty.swift */; };
|
||||
075131BA218322E000D3BFB9 /* NowPlayingInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075131B4218322E000D3BFB9 /* NowPlayingInfoController.swift */; };
|
||||
075131BB218322E000D3BFB9 /* NowPlayingInfoProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075131B5218322E000D3BFB9 /* NowPlayingInfoProperty.swift */; };
|
||||
075131BC218322E000D3BFB9 /* RemoteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075131B7218322E000D3BFB9 /* RemoteCommand.swift */; };
|
||||
075131BD218322E000D3BFB9 /* RemoteCommandController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 075131B8218322E000D3BFB9 /* RemoteCommandController.swift */; };
|
||||
076DFC5D2233CB1800A8D163 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076DFC5C2233CB1700A8D163 /* Event.swift */; };
|
||||
07756B74218C2D590023935E /* AudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07756B71218C2D590023935E /* AudioSession.swift */; };
|
||||
07756B75218C2D590023935E /* AudioSessionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07756B72218C2D590023935E /* AudioSessionController.swift */; };
|
||||
089FC967E3D8DD6212509D9C6AC7185B /* CwlCatchException.m in Sources */ = {isa = PBXBuildFile; fileRef = 25045F70284E802FFFF0F6EFD40A01C7 /* CwlCatchException.m */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
08EA01BACA4194902667F1FF29DC6265 /* MatcherProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945BF3F9EA32D635660DD7902B1A3D03 /* MatcherProtocols.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
095BD8D416719E425189E8E4963A8D4E /* TimeEventFrequency.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF4534C7E47CA5B502A19513117CBFC3 /* TimeEventFrequency.swift */; };
|
||||
@@ -25,12 +38,10 @@
|
||||
15FA146C0756A295AE40D1FBD77ABB6B /* AllPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD70D6C69732CC5E9DE6DC55873B188 /* AllPass.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
19FB07979AE2C3531B7F249AA237B30D /* BeGreaterThanOrEqualTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC94BD467B9F8E22E297008546EB659 /* BeGreaterThanOrEqualTo.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
1B1A26FD4363A19DEFED48BF5E5341BE /* NMBExceptionCapture.h in Headers */ = {isa = PBXBuildFile; fileRef = D73E015835BBDFB245780DE9E43ED9FE /* NMBExceptionCapture.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
1CAA71BDD1BCB7A8EBD8A890526774C6 /* RemoteCommandController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 689190491F4225B281ABC68343A24D61 /* RemoteCommandController.swift */; };
|
||||
1CFDB94DFF57F98BA7A40829DDB3CFC0 /* CwlPreconditionTesting.h in Headers */ = {isa = PBXBuildFile; fileRef = 20E6435A404A70F289B347C5815ABC10 /* CwlPreconditionTesting.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
1D230EC73A17C8494F279447636B6D51 /* Nimble.h in Headers */ = {isa = PBXBuildFile; fileRef = 572C9B621320D273AC417E7C04066AEA /* Nimble.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
1DC86A0AEBBA8A5B9353C4F7B2D4649A /* SwiftAudio-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B6541C48548E76AC573D95227051A3E8 /* SwiftAudio-dummy.m */; };
|
||||
1F626496F8B9ACBEA1C48D85D371790E /* ExampleMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0BC856D7B86412A484A3DD582F9841 /* ExampleMetadata.swift */; };
|
||||
213745669894F37CD02BED396AC2EFFA /* AudioSessionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006367118F9218EE3420DCEF343E7B4C /* AudioSessionController.swift */; };
|
||||
2722196E68C16CB6AE1C739A6632DDDD /* ToSucceed.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08DE2F262516A80F255EE2310F0EDD0 /* ToSucceed.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
275B03F03D701555A341DEFDA586D3F2 /* Nimble-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ADDE4EA63584E9BF3933026B32B66E2 /* Nimble-dummy.m */; };
|
||||
2A561B51439F9724CF97415032FFB649 /* NMBObjCMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDAE77638703F05830D6AF66FBCAE75 /* NMBObjCMatcher.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
@@ -79,17 +90,13 @@
|
||||
838A64480DA58965581A1750F3FED016 /* World+DSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB337D9B97833B31DBEAB7D4FB2AE0E /* World+DSL.swift */; };
|
||||
83C035F188A4F80F9696E6AD261DF00B /* Pods-SwiftAudio_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3350F0F9B5DDF05AE230A91E5FD38686 /* Pods-SwiftAudio_Tests-dummy.m */; };
|
||||
83F282C7E57F327D439604EB838FAA92 /* URL+FileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F09E00015A51CF7CCAC20DF7144AE44 /* URL+FileName.swift */; };
|
||||
8A77D5CE0D8380C1FD2FE859015C7DAA /* NowPlayingInfoProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4609750B9014770E50A7FF8F13C08BE9 /* NowPlayingInfoProperty.swift */; };
|
||||
8AA1573A6E580F689A48C3237CC36268 /* Contain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49C5B1205F3B26219A5F9471156353B /* Contain.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
8DECE3E28E934801E17DE5C0ACA24ABA /* RemoteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4339512935F0311EE3E2406CC855F /* RemoteCommand.swift */; };
|
||||
8F69EDCC09739459EB61749494F1E002 /* CwlCatchException.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E7DB1CD8C2C3735948E12DC35B0BED7 /* CwlCatchException.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
907EBF43A4A37DD92C61F0AEA6ED1330 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF45228F79391196CE7D3A18D70519B /* Filter.swift */; };
|
||||
9108613C28FC0D1A7948C6B4FE64932F /* Nimble-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = E4D833ED9179EEF01C24DCA76DD19097 /* Nimble-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
95ABBFEBA3AF22853E345BAAC8867FF3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 80C82283F763D9ABC7DD3BB91787CA8D /* XCTest.framework */; };
|
||||
95C261DC185368D41443743CBA636141 /* APError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011EE192D3B1D2A686C8C5D93E3AB0D0 /* APError.swift */; };
|
||||
9676A05220B174FC299049577162F1C8 /* MediaItemProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F815B046C467FFE21F47EEF4F76CBE2 /* MediaItemProperty.swift */; };
|
||||
9AADA2573B8098C022E8A390155DFD90 /* Functional.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EF3195721B0195371824114C2A4BEE /* Functional.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
9B94816B4EC1BBEC4E7971F04EA5192A /* SimpleAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C7830817928E0FBBAE48F19F730C2C /* SimpleAudioPlayer.swift */; };
|
||||
9D321DC100B95C35B6FF4D01BFCD6C07 /* ThrowError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FBFB0070F2E0C4399EAD76B169D24 /* ThrowError.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
9DEECB22E5869BB2ECD155D53946F939 /* NimbleXCTestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DA9613DD13D6685DDEE70B45F0ADFB6 /* NimbleXCTestHandler.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
A0871080516F7088835670597EB06674 /* CwlDarwinDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF5CEB6ECB6C3C4793E3828927021A57 /* CwlDarwinDefinitions.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
@@ -123,7 +130,6 @@
|
||||
C74928AC19FB27AA9D7AC4FC94A2D7F1 /* AVPlayerTimeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7481CD97B89126BA4A1C6FCC80366BD /* AVPlayerTimeObserver.swift */; };
|
||||
CCEC9E4FCE42FF652C0563E4F6BB0459 /* Example.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54476EB21A4C89C927FF42E858D9E3F /* Example.swift */; };
|
||||
D10E1F8FA9C618DFE1DE13BBF1A1B683 /* AudioItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C9AEEB071CD4A301C5C9CC7DB3CD15 /* AudioItem.swift */; };
|
||||
D1CC79EBF2498C6E01C4234330E33DA4 /* NowPlayingInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E72935CCE60D79BE7ECCC2A6645B9A6 /* NowPlayingInfoController.swift */; };
|
||||
D1F6601FD7B12B4DD24F04BB553F0B6D /* BeLessThan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B5E02942CB72732D6947AFD0D263FA /* BeLessThan.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
D5CEB8D0B830BAE216347870F154079D /* DSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63BAD0DA551276454D9ACE719DEFCEB1 /* DSL.m */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
DCD90D9E9A79C53BC7CD0FA8FA02961D /* BeEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6074FF9C29583B7D7FA989EDEDB550 /* BeEmpty.swift */; settings = {COMPILER_FLAGS = "-DPRODUCT_NAME=Nimble/Nimble"; }; };
|
||||
@@ -175,23 +181,34 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
006367118F9218EE3420DCEF343E7B4C /* AudioSessionController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AudioSessionController.swift; path = SwiftAudio/Classes/AudioSessionController.swift; sourceTree = "<group>"; };
|
||||
01056F9C4DEDD08119B7F98ECE8B2845 /* HaveCount.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = HaveCount.swift; path = Sources/Nimble/Matchers/HaveCount.swift; sourceTree = "<group>"; };
|
||||
011EE192D3B1D2A686C8C5D93E3AB0D0 /* APError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = APError.swift; path = SwiftAudio/Classes/APError.swift; sourceTree = "<group>"; };
|
||||
01DCEA2B70C5FE3C749141DED60AA3A1 /* AVPlayerObserver.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AVPlayerObserver.swift; sourceTree = "<group>"; };
|
||||
048A74934A0826237E15D12BBFA56C98 /* Quick-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Quick-umbrella.h"; sourceTree = "<group>"; };
|
||||
049D65258659A381E53BD1E11B7345E8 /* BeVoid.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BeVoid.swift; path = Sources/Nimble/Matchers/BeVoid.swift; sourceTree = "<group>"; };
|
||||
04C9AEEB071CD4A301C5C9CC7DB3CD15 /* AudioItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AudioItem.swift; path = SwiftAudio/Classes/AudioItem.swift; sourceTree = "<group>"; };
|
||||
074B0D6222289268001A45A9 /* NowPlayingInfoControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
074B0D642228929B001A45A9 /* NowPlayingInfoKeyValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoKeyValue.swift; sourceTree = "<group>"; };
|
||||
074B0D68222C23BB001A45A9 /* NowPlayingInfoCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoCenter.swift; sourceTree = "<group>"; };
|
||||
075131AC2182FF1600D3BFB9 /* AVPlayerWrapperProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerWrapperProtocol.swift; sourceTree = "<group>"; };
|
||||
075131AE21830D9500D3BFB9 /* AVPlayerWrapperDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerWrapperDelegate.swift; sourceTree = "<group>"; };
|
||||
075131B3218322E000D3BFB9 /* MediaItemProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItemProperty.swift; sourceTree = "<group>"; };
|
||||
075131B4218322E000D3BFB9 /* NowPlayingInfoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoController.swift; sourceTree = "<group>"; };
|
||||
075131B5218322E000D3BFB9 /* NowPlayingInfoProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoProperty.swift; sourceTree = "<group>"; };
|
||||
075131B7218322E000D3BFB9 /* RemoteCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteCommand.swift; sourceTree = "<group>"; };
|
||||
075131B8218322E000D3BFB9 /* RemoteCommandController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteCommandController.swift; sourceTree = "<group>"; };
|
||||
076DFC5C2233CB1700A8D163 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Event.swift; path = SwiftAudio/Classes/Event.swift; sourceTree = "<group>"; };
|
||||
07756B71218C2D590023935E /* AudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSession.swift; sourceTree = "<group>"; };
|
||||
07756B72218C2D590023935E /* AudioSessionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioSessionController.swift; sourceTree = "<group>"; };
|
||||
0A362FBD9BA860D03408EC59B8DAD715 /* QueuedAudioPlayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = QueuedAudioPlayer.swift; path = SwiftAudio/Classes/QueuedAudioPlayer.swift; sourceTree = "<group>"; };
|
||||
0B2E2D47B69C1FB3AC152895C90935DE /* NMBStringify.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = NMBStringify.m; path = Sources/NimbleObjectiveC/NMBStringify.m; sourceTree = "<group>"; };
|
||||
0B83D7847D2E148325682799F8D762FB /* AdapterProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AdapterProtocols.swift; path = Sources/Nimble/Adapters/AdapterProtocols.swift; sourceTree = "<group>"; };
|
||||
0C8C8954498E96A380A9E019FA368062 /* Pods_SwiftAudio_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_SwiftAudio_Tests.framework; path = "Pods-SwiftAudio_Tests.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0C8C8954498E96A380A9E019FA368062 /* Pods_SwiftAudio_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAudio_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0CFE2240B90DBC44697F16996798ADC0 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0D9BE9AED5D0D4A233A5F28CDAF64E5F /* NMBExceptionCapture.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = NMBExceptionCapture.m; path = Sources/NimbleObjectiveC/NMBExceptionCapture.m; sourceTree = "<group>"; };
|
||||
0E696C2BC0CF5F4035F7B6A9613C5608 /* BeAnInstanceOf.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BeAnInstanceOf.swift; path = Sources/Nimble/Matchers/BeAnInstanceOf.swift; sourceTree = "<group>"; };
|
||||
0E72935CCE60D79BE7ECCC2A6645B9A6 /* NowPlayingInfoController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NowPlayingInfoController.swift; path = SwiftAudio/Classes/NowPlayingInfoController.swift; sourceTree = "<group>"; };
|
||||
0F8B70224FF4BAA3EF856694DAEFEEEA /* BeGreaterThan.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BeGreaterThan.swift; path = Sources/Nimble/Matchers/BeGreaterThan.swift; sourceTree = "<group>"; };
|
||||
1038701997A03A7D55AAAC5A3E1D42F8 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = "<group>"; };
|
||||
1038701997A03A7D55AAAC5A3E1D42F8 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
1093B46C09355EB4CED5CA7DE7FDBFEE /* mach_excServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = mach_excServer.h; path = Carthage/Checkouts/CwlPreconditionTesting/Sources/CwlMachBadInstructionHandler/mach_excServer.h; sourceTree = "<group>"; };
|
||||
10D81A32DD08157E729CDA25855D4178 /* QCKDSL.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QCKDSL.h; path = Sources/QuickObjectiveC/DSL/QCKDSL.h; sourceTree = "<group>"; };
|
||||
160931E5B7C58FBCE27C6F77CD31065A /* Expectation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Expectation.swift; path = Sources/Nimble/Expectation.swift; sourceTree = "<group>"; };
|
||||
@@ -211,7 +228,7 @@
|
||||
2DEC6727EE6F37FE78F0B0879E3FC89C /* ExpectationMessage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpectationMessage.swift; path = Sources/Nimble/ExpectationMessage.swift; sourceTree = "<group>"; };
|
||||
2EE57213362ECA66BB3B1C08B2DB0C57 /* CwlCatchBadInstruction.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CwlCatchBadInstruction.swift; path = Carthage/Checkouts/CwlPreconditionTesting/Sources/CwlPreconditionTesting/CwlCatchBadInstruction.swift; sourceTree = "<group>"; };
|
||||
2F45FD38D2C696978A840807E542F9DA /* ExampleGroup.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExampleGroup.swift; path = Sources/Quick/ExampleGroup.swift; sourceTree = "<group>"; };
|
||||
2FD31C5F451C5A3BD436B937CE169BAD /* SwiftAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SwiftAudio.framework; path = SwiftAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2FD31C5F451C5A3BD436B937CE169BAD /* SwiftAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3350F0F9B5DDF05AE230A91E5FD38686 /* Pods-SwiftAudio_Tests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-SwiftAudio_Tests-dummy.m"; sourceTree = "<group>"; };
|
||||
336E11B7F8854A9118FB070A9E34CF78 /* Await.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Await.swift; path = Sources/Nimble/Utils/Await.swift; sourceTree = "<group>"; };
|
||||
354FCCFF316C6B11FFA6BB86B5B58D43 /* NSBundle+CurrentTestBundle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSBundle+CurrentTestBundle.swift"; path = "Sources/Quick/NSBundle+CurrentTestBundle.swift"; sourceTree = "<group>"; };
|
||||
@@ -220,7 +237,6 @@
|
||||
3D0BC856D7B86412A484A3DD582F9841 /* ExampleMetadata.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExampleMetadata.swift; path = Sources/Quick/ExampleMetadata.swift; sourceTree = "<group>"; };
|
||||
3EB72EE755307D2502E70A6B529B73EC /* QuickSpec.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QuickSpec.h; path = Sources/QuickObjectiveC/QuickSpec.h; sourceTree = "<group>"; };
|
||||
42CE60C662D9A1382B4BF26914AC82D0 /* DSL.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DSL.h; path = Sources/NimbleObjectiveC/DSL.h; sourceTree = "<group>"; };
|
||||
4609750B9014770E50A7FF8F13C08BE9 /* NowPlayingInfoProperty.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NowPlayingInfoProperty.swift; path = SwiftAudio/Classes/NowPlayingInfoProperty.swift; sourceTree = "<group>"; };
|
||||
4760419BE7E8BC5915D5CFF14109A979 /* Pods-SwiftAudio_Tests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftAudio_Tests-resources.sh"; sourceTree = "<group>"; };
|
||||
48FB816DE4B6A4BFDD305F58A6472925 /* ThrowAssertion.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ThrowAssertion.swift; path = Sources/Nimble/Matchers/ThrowAssertion.swift; sourceTree = "<group>"; };
|
||||
4ADDE4EA63584E9BF3933026B32B66E2 /* Nimble-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Nimble-dummy.m"; sourceTree = "<group>"; };
|
||||
@@ -243,19 +259,18 @@
|
||||
5F4FB27486313C6A081388FCCB02EC16 /* QuickSpecBase.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QuickSpecBase.m; path = Sources/QuickSpecBase/QuickSpecBase.m; sourceTree = "<group>"; };
|
||||
5FF5498ACD8246B3B1EC6CBFCAD4C8D7 /* Quick-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Quick-prefix.pch"; sourceTree = "<group>"; };
|
||||
60B070F60A08FF5EF7CF3D1A69143000 /* NMBExpectation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NMBExpectation.swift; path = Sources/Nimble/Adapters/NMBExpectation.swift; sourceTree = "<group>"; };
|
||||
60D239C6021277D481ED86E2E29B1670 /* SwiftAudio.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; path = SwiftAudio.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
60D239C6021277D481ED86E2E29B1670 /* SwiftAudio.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; path = SwiftAudio.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
63BAD0DA551276454D9ACE719DEFCEB1 /* DSL.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DSL.m; path = Sources/NimbleObjectiveC/DSL.m; sourceTree = "<group>"; };
|
||||
660D7D4C3B82FFD945DFA23D0C1C6A9E /* SatisfyAnyOf.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SatisfyAnyOf.swift; path = Sources/Nimble/Matchers/SatisfyAnyOf.swift; sourceTree = "<group>"; };
|
||||
66323C82A0A7CB5FB54B5F774BEC72DC /* Quick.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Quick.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
66323C82A0A7CB5FB54B5F774BEC72DC /* Quick.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6708E82D864D86CC9A66E3A038B6FDB9 /* ExampleHooks.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExampleHooks.swift; path = Sources/Quick/Hooks/ExampleHooks.swift; sourceTree = "<group>"; };
|
||||
689190491F4225B281ABC68343A24D61 /* RemoteCommandController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RemoteCommandController.swift; path = SwiftAudio/Classes/RemoteCommandController.swift; sourceTree = "<group>"; };
|
||||
6CCBA4A43D738FAE7BC1910666153FD0 /* SwiftAudio.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftAudio.xcconfig; sourceTree = "<group>"; };
|
||||
6DEE0ABD257E7A562F3A8FAFE03E9FBC /* Pods-SwiftAudio_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftAudio_Tests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
6F09E00015A51CF7CCAC20DF7144AE44 /* URL+FileName.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "URL+FileName.swift"; path = "Sources/Quick/URL+FileName.swift"; sourceTree = "<group>"; };
|
||||
70B5E02942CB72732D6947AFD0D263FA /* BeLessThan.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BeLessThan.swift; path = Sources/Nimble/Matchers/BeLessThan.swift; sourceTree = "<group>"; };
|
||||
731667DB8FCC21C2D4FEAE6CC1FED184 /* Errors.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Errors.swift; path = Sources/Nimble/Utils/Errors.swift; sourceTree = "<group>"; };
|
||||
74320E6F1A8A9105DC422E9C6DFE820C /* AssertionDispatcher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssertionDispatcher.swift; path = Sources/Nimble/Adapters/AssertionDispatcher.swift; sourceTree = "<group>"; };
|
||||
747BC4EDBF186DED5F6ECE69B29801A9 /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Nimble.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
747BC4EDBF186DED5F6ECE69B29801A9 /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AB337D9B97833B31DBEAB7D4FB2AE0E /* World+DSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "World+DSL.swift"; path = "Sources/Quick/DSL/World+DSL.swift"; sourceTree = "<group>"; };
|
||||
7C47506434F8C86E58A02928CC65CC10 /* Pods-SwiftAudio_Tests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-SwiftAudio_Tests-acknowledgements.plist"; sourceTree = "<group>"; };
|
||||
7CF92AE58937B3AD4BA6DA7134DA9F52 /* AudioPlayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AudioPlayer.swift; path = SwiftAudio/Classes/AudioPlayer.swift; sourceTree = "<group>"; };
|
||||
@@ -265,17 +280,15 @@
|
||||
81B10C2CFD2B4D759CF041A45CF58FF2 /* CwlBadInstructionException.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CwlBadInstructionException.swift; path = Carthage/Checkouts/CwlPreconditionTesting/Sources/CwlPreconditionTesting/CwlBadInstructionException.swift; sourceTree = "<group>"; };
|
||||
823F6A12075FF0A33C70898ECC8A145B /* NSString+C99ExtendedIdentifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "NSString+C99ExtendedIdentifier.swift"; path = "Sources/Quick/NSString+C99ExtendedIdentifier.swift"; sourceTree = "<group>"; };
|
||||
8350374D2D2D105674580DABB85F37FF /* AVPlayerWrapper.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AVPlayerWrapper.swift; sourceTree = "<group>"; };
|
||||
84A291A79F9C752A91C561C0AFB7B957 /* Pods_SwiftAudio_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_SwiftAudio_Example.framework; path = "Pods-SwiftAudio_Example.framework"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
84A291A79F9C752A91C561C0AFB7B957 /* Pods_SwiftAudio_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftAudio_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
85A12CC592518974E5AF8CC73C019039 /* Quick.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = Quick.modulemap; sourceTree = "<group>"; };
|
||||
879D4131C2851B0A039CD43D72FA9AF5 /* Stringers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Stringers.swift; path = Sources/Nimble/Utils/Stringers.swift; sourceTree = "<group>"; };
|
||||
87C7830817928E0FBBAE48F19F730C2C /* SimpleAudioPlayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SimpleAudioPlayer.swift; path = SwiftAudio/Classes/SimpleAudioPlayer.swift; sourceTree = "<group>"; };
|
||||
8967DA4BBBE5E17E301D3C56BE293A95 /* QuickSpec.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QuickSpec.m; path = Sources/QuickObjectiveC/QuickSpec.m; sourceTree = "<group>"; };
|
||||
8CA0648396C81EDE0A9B7C711638FA8B /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
8D87AE93CC56AA3DD5FA80EF8564C9C2 /* EndWith.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EndWith.swift; path = Sources/Nimble/Matchers/EndWith.swift; sourceTree = "<group>"; };
|
||||
8DA9613DD13D6685DDEE70B45F0ADFB6 /* NimbleXCTestHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NimbleXCTestHandler.swift; path = Sources/Nimble/Adapters/NimbleXCTestHandler.swift; sourceTree = "<group>"; };
|
||||
8DBDFCD12FE89ED7977BAEC1F0EC7101 /* XCTestSuite+QuickTestSuiteBuilder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "XCTestSuite+QuickTestSuiteBuilder.m"; path = "Sources/QuickObjectiveC/XCTestSuite+QuickTestSuiteBuilder.m"; sourceTree = "<group>"; };
|
||||
8F815B046C467FFE21F47EEF4F76CBE2 /* MediaItemProperty.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MediaItemProperty.swift; path = SwiftAudio/Classes/MediaItemProperty.swift; sourceTree = "<group>"; };
|
||||
93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
|
||||
942F4368934964E307D71356A565E34F /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
945BF3F9EA32D635660DD7902B1A3D03 /* MatcherProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MatcherProtocols.swift; path = Sources/Nimble/Matchers/MatcherProtocols.swift; sourceTree = "<group>"; };
|
||||
993D3D81BF52A44266E4840EF7D5D995 /* AssertionRecorder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssertionRecorder.swift; path = Sources/Nimble/Adapters/AssertionRecorder.swift; sourceTree = "<group>"; };
|
||||
@@ -286,7 +299,6 @@
|
||||
9FC94BD467B9F8E22E297008546EB659 /* BeGreaterThanOrEqualTo.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BeGreaterThanOrEqualTo.swift; path = Sources/Nimble/Matchers/BeGreaterThanOrEqualTo.swift; sourceTree = "<group>"; };
|
||||
A474607214D558C1FDF0E33F10A70CC2 /* PostNotification.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PostNotification.swift; path = Sources/Nimble/Matchers/PostNotification.swift; sourceTree = "<group>"; };
|
||||
A54476EB21A4C89C927FF42E858D9E3F /* Example.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Example.swift; path = Sources/Quick/Example.swift; sourceTree = "<group>"; };
|
||||
A9C4339512935F0311EE3E2406CC855F /* RemoteCommand.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RemoteCommand.swift; path = SwiftAudio/Classes/RemoteCommand.swift; sourceTree = "<group>"; };
|
||||
AF2AA3686E2E5C992300580BE170EF0B /* Quick.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Quick.xcconfig; sourceTree = "<group>"; };
|
||||
AFE4BE7D9282807F0AEB8591BD9031E6 /* Nimble.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Nimble.xcconfig; sourceTree = "<group>"; };
|
||||
B110998EFA9DEB0A41CF492290C3CC9E /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -306,7 +318,7 @@
|
||||
C965EB7D2ABC715652F428F0755CC37C /* Pods-SwiftAudio_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-SwiftAudio_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
CA2B83B52F99EEA836F6F0669A3B9B3A /* DSL.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DSL.swift; path = Sources/Quick/DSL/DSL.swift; sourceTree = "<group>"; };
|
||||
CA4741D70CAC2E64FB39C437CE5CF9AF /* SwiftAudio.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SwiftAudio.modulemap; sourceTree = "<group>"; };
|
||||
CD78111E631F1A9FBB37A70A9EF3B187 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = "<group>"; };
|
||||
CD78111E631F1A9FBB37A70A9EF3B187 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
CFCDA081DA07D782D75DA9C33E476214 /* SourceLocation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SourceLocation.swift; path = Sources/Nimble/Utils/SourceLocation.swift; sourceTree = "<group>"; };
|
||||
D0463AFE8D72243A6EB984085BE04A65 /* BeLessThanOrEqual.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BeLessThanOrEqual.swift; path = Sources/Nimble/Matchers/BeLessThanOrEqual.swift; sourceTree = "<group>"; };
|
||||
D08DE2F262516A80F255EE2310F0EDD0 /* ToSucceed.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ToSucceed.swift; path = Sources/Nimble/Matchers/ToSucceed.swift; sourceTree = "<group>"; };
|
||||
@@ -328,7 +340,7 @@
|
||||
EA5E86ABAB681E9B91D7D7CD0FCD5EB2 /* Pods-SwiftAudio_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-SwiftAudio_Example-frameworks.sh"; sourceTree = "<group>"; };
|
||||
EDE8144940E39AB93895BF39F2E9FEBC /* Pods-SwiftAudio_Tests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-SwiftAudio_Tests-umbrella.h"; sourceTree = "<group>"; };
|
||||
EE8BB96B10E6B8555DD8E78B0E39FEBE /* World.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = World.swift; path = Sources/Quick/World.swift; sourceTree = "<group>"; };
|
||||
EED52EE491F66BC008AB993C58365EBE /* mach_excServer.c */ = {isa = PBXFileReference; includeInIndex = 1; name = mach_excServer.c; path = Carthage/Checkouts/CwlPreconditionTesting/Sources/CwlMachBadInstructionHandler/mach_excServer.c; sourceTree = "<group>"; };
|
||||
EED52EE491F66BC008AB993C58365EBE /* mach_excServer.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; name = mach_excServer.c; path = Carthage/Checkouts/CwlPreconditionTesting/Sources/CwlMachBadInstructionHandler/mach_excServer.c; sourceTree = "<group>"; };
|
||||
EF4534C7E47CA5B502A19513117CBFC3 /* TimeEventFrequency.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TimeEventFrequency.swift; path = SwiftAudio/Classes/TimeEventFrequency.swift; sourceTree = "<group>"; };
|
||||
EF5CEB6ECB6C3C4793E3828927021A57 /* CwlDarwinDefinitions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CwlDarwinDefinitions.swift; path = Carthage/Checkouts/CwlPreconditionTesting/Sources/CwlPreconditionTesting/CwlDarwinDefinitions.swift; sourceTree = "<group>"; };
|
||||
F031D2A41C0E2D94B334BC31754F2F09 /* AVPlayerWrapperState.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AVPlayerWrapperState.swift; sourceTree = "<group>"; };
|
||||
@@ -389,6 +401,40 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
075131B2218322E000D3BFB9 /* NowPlayingInfoController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
075131B3218322E000D3BFB9 /* MediaItemProperty.swift */,
|
||||
075131B4218322E000D3BFB9 /* NowPlayingInfoController.swift */,
|
||||
075131B5218322E000D3BFB9 /* NowPlayingInfoProperty.swift */,
|
||||
074B0D6222289268001A45A9 /* NowPlayingInfoControllerProtocol.swift */,
|
||||
074B0D642228929B001A45A9 /* NowPlayingInfoKeyValue.swift */,
|
||||
074B0D68222C23BB001A45A9 /* NowPlayingInfoCenter.swift */,
|
||||
);
|
||||
name = NowPlayingInfoController;
|
||||
path = SwiftAudio/Classes/NowPlayingInfoController;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
075131B6218322E000D3BFB9 /* RemoteCommandController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
075131B7218322E000D3BFB9 /* RemoteCommand.swift */,
|
||||
075131B8218322E000D3BFB9 /* RemoteCommandController.swift */,
|
||||
);
|
||||
name = RemoteCommandController;
|
||||
path = SwiftAudio/Classes/RemoteCommandController;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
07756B6F218C2D590023935E /* AudioSessionController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
07756B72218C2D590023935E /* AudioSessionController.swift */,
|
||||
07756B71218C2D590023935E /* AudioSession.swift */,
|
||||
);
|
||||
name = AudioSessionController;
|
||||
path = SwiftAudio/Classes/AudioSessionController;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1E85F0F44277212282918D8D5DDB3588 /* iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -464,7 +510,6 @@
|
||||
8DBDFCD12FE89ED7977BAEC1F0EC7101 /* XCTestSuite+QuickTestSuiteBuilder.m */,
|
||||
3E8F7D7BA6F7BD122DBB624992079C91 /* Support Files */,
|
||||
);
|
||||
name = Quick;
|
||||
path = Quick;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -481,6 +526,8 @@
|
||||
children = (
|
||||
8350374D2D2D105674580DABB85F37FF /* AVPlayerWrapper.swift */,
|
||||
F031D2A41C0E2D94B334BC31754F2F09 /* AVPlayerWrapperState.swift */,
|
||||
075131AC2182FF1600D3BFB9 /* AVPlayerWrapperProtocol.swift */,
|
||||
075131AE21830D9500D3BFB9 /* AVPlayerWrapperDelegate.swift */,
|
||||
);
|
||||
name = AVPlayerWrapper;
|
||||
path = SwiftAudio/Classes/AVPlayerWrapper;
|
||||
@@ -661,7 +708,6 @@
|
||||
56AED7BB8EBC87A7394B1B4309775DC2 /* XCTestObservationCenter+Register.m */,
|
||||
5F94F4B7D93544A44CDA76AFC8704CB7 /* Support Files */,
|
||||
);
|
||||
name = Nimble;
|
||||
path = Nimble;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -671,16 +717,13 @@
|
||||
011EE192D3B1D2A686C8C5D93E3AB0D0 /* APError.swift */,
|
||||
04C9AEEB071CD4A301C5C9CC7DB3CD15 /* AudioItem.swift */,
|
||||
7CF92AE58937B3AD4BA6DA7134DA9F52 /* AudioPlayer.swift */,
|
||||
006367118F9218EE3420DCEF343E7B4C /* AudioSessionController.swift */,
|
||||
8F815B046C467FFE21F47EEF4F76CBE2 /* MediaItemProperty.swift */,
|
||||
0E72935CCE60D79BE7ECCC2A6645B9A6 /* NowPlayingInfoController.swift */,
|
||||
4609750B9014770E50A7FF8F13C08BE9 /* NowPlayingInfoProperty.swift */,
|
||||
0A362FBD9BA860D03408EC59B8DAD715 /* QueuedAudioPlayer.swift */,
|
||||
7D4763A422F83644D194E97981775831 /* QueueManager.swift */,
|
||||
A9C4339512935F0311EE3E2406CC855F /* RemoteCommand.swift */,
|
||||
689190491F4225B281ABC68343A24D61 /* RemoteCommandController.swift */,
|
||||
87C7830817928E0FBBAE48F19F730C2C /* SimpleAudioPlayer.swift */,
|
||||
EF4534C7E47CA5B502A19513117CBFC3 /* TimeEventFrequency.swift */,
|
||||
076DFC5C2233CB1700A8D163 /* Event.swift */,
|
||||
07756B6F218C2D590023935E /* AudioSessionController */,
|
||||
075131B2218322E000D3BFB9 /* NowPlayingInfoController */,
|
||||
075131B6218322E000D3BFB9 /* RemoteCommandController */,
|
||||
581526FDBE4DC5C1C3F143696A8DE42E /* AVPlayerWrapper */,
|
||||
A6AF9F48F4B25035E227BFDAE847B384 /* Observer */,
|
||||
5D68DC904FAAE9CFC1059C6B3F4BB87D /* Pod */,
|
||||
@@ -864,6 +907,11 @@
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0930;
|
||||
LastUpgradeCheck = 0930;
|
||||
TargetAttributes = {
|
||||
1AB61EB02FDF0033DCB1F8416419F110 = {
|
||||
LastSwiftMigration = 1010;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
@@ -907,25 +955,31 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
075131BD218322E000D3BFB9 /* RemoteCommandController.swift in Sources */,
|
||||
074B0D652228929B001A45A9 /* NowPlayingInfoKeyValue.swift in Sources */,
|
||||
95C261DC185368D41443743CBA636141 /* APError.swift in Sources */,
|
||||
075131BB218322E000D3BFB9 /* NowPlayingInfoProperty.swift in Sources */,
|
||||
D10E1F8FA9C618DFE1DE13BBF1A1B683 /* AudioItem.swift in Sources */,
|
||||
42249CE127313A81E6DF9F492BC3FD60 /* AudioPlayer.swift in Sources */,
|
||||
213745669894F37CD02BED396AC2EFFA /* AudioSessionController.swift in Sources */,
|
||||
075131B9218322E000D3BFB9 /* MediaItemProperty.swift in Sources */,
|
||||
121F6F4C3C3FBD49FBFF3BC91205C11B /* AVPlayerItemNotificationObserver.swift in Sources */,
|
||||
E10CAB33DDA27C101EF36E8923046511 /* AVPlayerItemObserver.swift in Sources */,
|
||||
13AFC3FFA6B2175A542C1279345CAAA3 /* AVPlayerObserver.swift in Sources */,
|
||||
C74928AC19FB27AA9D7AC4FC94A2D7F1 /* AVPlayerTimeObserver.swift in Sources */,
|
||||
07756B74218C2D590023935E /* AudioSession.swift in Sources */,
|
||||
076DFC5D2233CB1800A8D163 /* Event.swift in Sources */,
|
||||
315B8C7DDF47918E6F996F8AC45C6BB7 /* AVPlayerWrapper.swift in Sources */,
|
||||
F462AF5848880C2C1791D0C7F943879A /* AVPlayerWrapperState.swift in Sources */,
|
||||
9676A05220B174FC299049577162F1C8 /* MediaItemProperty.swift in Sources */,
|
||||
D1CC79EBF2498C6E01C4234330E33DA4 /* NowPlayingInfoController.swift in Sources */,
|
||||
8A77D5CE0D8380C1FD2FE859015C7DAA /* NowPlayingInfoProperty.swift in Sources */,
|
||||
074B0D69222C23BB001A45A9 /* NowPlayingInfoCenter.swift in Sources */,
|
||||
075131AD2182FF1600D3BFB9 /* AVPlayerWrapperProtocol.swift in Sources */,
|
||||
075131BC218322E000D3BFB9 /* RemoteCommand.swift in Sources */,
|
||||
36DAEF0CCEB4AEFA64DFAF8197FD5C68 /* QueuedAudioPlayer.swift in Sources */,
|
||||
075131BA218322E000D3BFB9 /* NowPlayingInfoController.swift in Sources */,
|
||||
47B306C3AA1AA9EB82EE2FDE5267B13C /* QueueManager.swift in Sources */,
|
||||
8DECE3E28E934801E17DE5C0ACA24ABA /* RemoteCommand.swift in Sources */,
|
||||
1CAA71BDD1BCB7A8EBD8A890526774C6 /* RemoteCommandController.swift in Sources */,
|
||||
9B94816B4EC1BBEC4E7971F04EA5192A /* SimpleAudioPlayer.swift in Sources */,
|
||||
07756B75218C2D590023935E /* AudioSessionController.swift in Sources */,
|
||||
075131AF21830D9500D3BFB9 /* AVPlayerWrapperDelegate.swift in Sources */,
|
||||
1DC86A0AEBBA8A5B9353C4F7B2D4649A /* SwiftAudio-dummy.m in Sources */,
|
||||
074B0D6322289268001A45A9 /* NowPlayingInfoControllerProtocol.swift in Sources */,
|
||||
095BD8D416719E425189E8E4963A8D4E /* TimeEventFrequency.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1192,7 +1246,7 @@
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -1412,7 +1466,7 @@
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1AB61EB02FDF0033DCB1F8416419F110"
|
||||
BuildableName = "SwiftAudio.framework"
|
||||
BlueprintName = "SwiftAudio"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1AB61EB02FDF0033DCB1F8416419F110"
|
||||
BuildableName = "SwiftAudio.framework"
|
||||
BlueprintName = "SwiftAudio"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -24,14 +24,19 @@
|
||||
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */; };
|
||||
074A6485205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */; };
|
||||
074A6487205E59B60083D868 /* AVPlayerWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */; };
|
||||
074B0D67222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D66222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift */; };
|
||||
074B0D6B222C247B001A45A9 /* NowPlayingInfoCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D6A222C247B001A45A9 /* NowPlayingInfoCenter.swift */; };
|
||||
074B0D6D222C24DE001A45A9 /* NowPlayingInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B0D6C222C24DE001A45A9 /* NowPlayingInfoController.swift */; };
|
||||
076DFC5F22345EAF00A8D163 /* AudioPlayerEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076DFC5E22345EAF00A8D163 /* AudioPlayerEventTests.swift */; };
|
||||
07732651205EACA300C4D1CD /* WAV-MP3.wav in Resources */ = {isa = PBXBuildFile; fileRef = 07732650205EACA300C4D1CD /* WAV-MP3.wav */; };
|
||||
07732653205EB1B500C4D1CD /* nasa_throttle_up.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */; };
|
||||
07732654205ECA8B00C4D1CD /* WAV-MP3.wav in Resources */ = {isa = PBXBuildFile; fileRef = 07732650205EACA300C4D1CD /* WAV-MP3.wav */; };
|
||||
07732655205ECE1C00C4D1CD /* nasa_throttle_up.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */; };
|
||||
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0775575820668B020002C6A1 /* QueueManagerTests.swift */; };
|
||||
07756B69218A4E870023935E /* AudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07756B68218A4E870023935E /* AudioSession.swift */; };
|
||||
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */; };
|
||||
07CC171C213E912E005F880E /* SimpleAudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */; };
|
||||
07DBB1E1212C17E600BB4278 /* QueuedAudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */; };
|
||||
07EB8EE2222869B2000197DE /* NowPlayingInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07EB8EE022286980000197DE /* NowPlayingInfoTests.swift */; };
|
||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
|
||||
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
|
||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
|
||||
@@ -65,12 +70,17 @@
|
||||
074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerTimeObserverTests.swift; sourceTree = "<group>"; };
|
||||
074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItemNotificationObserverTests.swift; sourceTree = "<group>"; };
|
||||
074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerWrapperTests.swift; sourceTree = "<group>"; };
|
||||
074B0D66222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoControllerTests.swift; sourceTree = "<group>"; };
|
||||
074B0D6A222C247B001A45A9 /* NowPlayingInfoCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoCenter.swift; sourceTree = "<group>"; };
|
||||
074B0D6C222C24DE001A45A9 /* NowPlayingInfoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoController.swift; sourceTree = "<group>"; };
|
||||
076DFC5E22345EAF00A8D163 /* AudioPlayerEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerEventTests.swift; sourceTree = "<group>"; };
|
||||
07732650205EACA300C4D1CD /* WAV-MP3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "WAV-MP3.wav"; sourceTree = "<group>"; };
|
||||
07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = nasa_throttle_up.mp3; sourceTree = "<group>"; };
|
||||
0775575820668B020002C6A1 /* QueueManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueManagerTests.swift; sourceTree = "<group>"; };
|
||||
07756B68218A4E870023935E /* AudioSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSession.swift; sourceTree = "<group>"; };
|
||||
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerItemObserverTests.swift; sourceTree = "<group>"; };
|
||||
07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleAudioPlayerTests.swift; sourceTree = "<group>"; };
|
||||
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuedAudioPlayerTests.swift; sourceTree = "<group>"; };
|
||||
07EB8EE022286980000197DE /* NowPlayingInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfoTests.swift; sourceTree = "<group>"; };
|
||||
521F3AEC1228A2FA2637355F /* Pods-SwiftAudio_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftAudio_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftAudio_Tests/Pods-SwiftAudio_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAudio_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -124,6 +134,16 @@
|
||||
path = Source;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
07756B67218A4E640023935E /* Mocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
07756B68218A4E870023935E /* AudioSession.swift */,
|
||||
074B0D6A222C247B001A45A9 /* NowPlayingInfoCenter.swift */,
|
||||
074B0D6C222C24DE001A45A9 /* NowPlayingInfoController.swift */,
|
||||
);
|
||||
path = Mocks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACC71AFB9204008FA782 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -175,6 +195,7 @@
|
||||
607FACE81AFB9204008FA782 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
07756B67218A4E640023935E /* Mocks */,
|
||||
0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */,
|
||||
607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */,
|
||||
074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */,
|
||||
@@ -184,7 +205,9 @@
|
||||
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */,
|
||||
0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */,
|
||||
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */,
|
||||
07CC171A213E912A005F880E /* SimpleAudioPlayerTests.swift */,
|
||||
07EB8EE022286980000197DE /* NowPlayingInfoTests.swift */,
|
||||
074B0D66222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift */,
|
||||
076DFC5E22345EAF00A8D163 /* AudioPlayerEventTests.swift */,
|
||||
0708ED712116E91300EB29BD /* Source */,
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */,
|
||||
);
|
||||
@@ -278,13 +301,13 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0830;
|
||||
LastUpgradeCheck = 0830;
|
||||
LastUpgradeCheck = 1010;
|
||||
ORGANIZATIONNAME = CocoaPods;
|
||||
TargetAttributes = {
|
||||
607FACCF1AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
DevelopmentTeam = HPNZWPB9JK;
|
||||
LastSwiftMigration = 0900;
|
||||
LastSwiftMigration = 1010;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
enabled = 1;
|
||||
@@ -294,7 +317,7 @@
|
||||
607FACE41AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
DevelopmentTeam = HPNZWPB9JK;
|
||||
LastSwiftMigration = 0900;
|
||||
LastSwiftMigration = 1010;
|
||||
TestTargetID = 607FACCF1AFB9204008FA782;
|
||||
};
|
||||
};
|
||||
@@ -443,17 +466,22 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
07756B69218A4E870023935E /* AudioSession.swift in Sources */,
|
||||
074B0D67222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift in Sources */,
|
||||
0708ED702116E89900EB29BD /* Source.swift in Sources */,
|
||||
0708ED742116EE0100EB29BD /* AudioPlayerTests.swift in Sources */,
|
||||
07CC171C213E912E005F880E /* SimpleAudioPlayerTests.swift in Sources */,
|
||||
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */,
|
||||
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */,
|
||||
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */,
|
||||
0708ED6C2116DA4C00EB29BD /* AudioSessionControllerTests.swift in Sources */,
|
||||
074B0D6B222C247B001A45A9 /* NowPlayingInfoCenter.swift in Sources */,
|
||||
07DBB1E1212C17E600BB4278 /* QueuedAudioPlayerTests.swift in Sources */,
|
||||
076DFC5F22345EAF00A8D163 /* AudioPlayerEventTests.swift in Sources */,
|
||||
074B0D6D222C24DE001A45A9 /* NowPlayingInfoController.swift in Sources */,
|
||||
074A6485205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift in Sources */,
|
||||
607FACEC1AFB9204008FA782 /* AVPlayerObserverTests.swift in Sources */,
|
||||
074A6487205E59B60083D868 /* AVPlayerWrapperTests.swift in Sources */,
|
||||
07EB8EE2222869B2000197DE /* NowPlayingInfoTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -499,12 +527,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -552,12 +582,14 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -598,8 +630,8 @@
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -615,8 +647,8 @@
|
||||
MODULE_NAME = ExampleApp;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -637,8 +669,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -656,8 +687,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "1010"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
<dict>
|
||||
<key>BuildSystemType</key>
|
||||
<string>Original</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
application.beginReceivingRemoteControlEvents()
|
||||
|
||||
@@ -13,7 +13,7 @@ import SwiftAudio
|
||||
class AudioController {
|
||||
|
||||
static let shared = AudioController()
|
||||
let player = QueuedAudioPlayer()
|
||||
let player: QueuedAudioPlayer
|
||||
let audioSessionController = AudioSessionController.shared
|
||||
|
||||
let sources: [AudioItem] = [
|
||||
@@ -24,8 +24,12 @@ class AudioController {
|
||||
]
|
||||
|
||||
init() {
|
||||
let controller = RemoteCommandController()
|
||||
player = QueuedAudioPlayer(remoteCommandController: controller)
|
||||
player.remoteCommands = [
|
||||
.stop,
|
||||
.play,
|
||||
.pause,
|
||||
.togglePlayPause,
|
||||
.next,
|
||||
.previous,
|
||||
|
||||
@@ -46,7 +46,7 @@ extension QueueViewController: UITableViewDataSource, UITableViewDelegate {
|
||||
case 0:
|
||||
return 1
|
||||
case 1:
|
||||
return controller.player.nextItems.count ?? 0
|
||||
return controller.player.nextItems.count
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -27,14 +27,17 @@ class ViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
controller.player.delegate = self
|
||||
controller.player.event.stateChange.addListener(self, handleAudioPlayerStateChange)
|
||||
controller.player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed)
|
||||
controller.player.event.seek.addListener(self, handleAudioPlayerDidSeek)
|
||||
controller.player.event.updateDuration.addListener(self, handleAudioPlayerUpdateDuration)
|
||||
}
|
||||
|
||||
@IBAction func togglePlay(_ sender: Any) {
|
||||
if (!controller.audioSessionController.audioSessionIsActive) {
|
||||
try? controller.audioSessionController.activateSession()
|
||||
}
|
||||
try? controller.player.togglePlaying()
|
||||
controller.player.togglePlaying()
|
||||
}
|
||||
|
||||
@IBAction func previous(_ sender: Any) {
|
||||
@@ -50,7 +53,7 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
@IBAction func scrubbing(_ sender: UISlider) {
|
||||
try? controller.player.seek(to: Double(slider.value))
|
||||
controller.player.seek(to: Double(slider.value))
|
||||
}
|
||||
|
||||
@IBAction func scrubbingValueChanged(_ sender: UISlider) {
|
||||
@@ -58,63 +61,59 @@ class ViewController: UIViewController {
|
||||
elapsedTimeLabel.text = value.secondsToString()
|
||||
remainingTimeLabel.text = (controller.player.duration - value).secondsToString()
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: AudioPlayerDelegate {
|
||||
|
||||
func audioPlayer(playerDidChangeState state: AVPlayerWrapperState) {
|
||||
func updateTimeValues() {
|
||||
self.slider.maximumValue = Float(self.controller.player.duration)
|
||||
self.slider.setValue(Float(self.controller.player.currentTime), animated: true)
|
||||
self.elapsedTimeLabel.text = self.controller.player.currentTime.secondsToString()
|
||||
self.remainingTimeLabel.text = (self.controller.player.duration - self.controller.player.currentTime).secondsToString()
|
||||
}
|
||||
|
||||
func updateMetaData() {
|
||||
if let item = controller.player.currentItem {
|
||||
titleLabel.text = item.getTitle()
|
||||
artistLabel.text = item.getArtist()
|
||||
item.getArtwork({ (image) in
|
||||
self.imageView.image = image
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setPlayButtonState(forAudioPlayerState state: AudioPlayerState) {
|
||||
playButton.setTitle(state == .playing ? "Pause" : "Play", for: .normal)
|
||||
|
||||
switch state {
|
||||
case .ready:
|
||||
|
||||
if let item = controller.player.currentItem {
|
||||
titleLabel.text = item.getTitle()
|
||||
artistLabel.text = item.getArtist()
|
||||
item.getArtwork({ (image) in
|
||||
self.imageView.image = image
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - AudioPlayer Event Handlers
|
||||
|
||||
func handleAudioPlayerStateChange(data: AudioPlayer.StateChangeEventData) {
|
||||
DispatchQueue.main.async {
|
||||
self.setPlayButtonState(forAudioPlayerState: data)
|
||||
switch data {
|
||||
case .ready:
|
||||
self.updateMetaData()
|
||||
self.updateTimeValues()
|
||||
case .loading, .playing, .paused, .idle:
|
||||
self.updateTimeValues()
|
||||
}
|
||||
|
||||
slider.maximumValue = Float(controller.player.duration)
|
||||
slider.setValue(Float(controller.player.currentTime), animated: true)
|
||||
|
||||
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
|
||||
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
|
||||
|
||||
case .loading, .playing, .paused, .idle:
|
||||
slider.maximumValue = Float(controller.player.duration)
|
||||
slider.setValue(Float(controller.player.currentTime), animated: true)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func audioPlayerItemDidComplete() {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(secondsElapsed seconds: Double) {
|
||||
func handleAudioPlayerSecondElapsed(data: AudioPlayer.SecondElapseEventData) {
|
||||
if !isScrubbing {
|
||||
slider.setValue(Float(seconds), animated: false)
|
||||
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
|
||||
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
|
||||
DispatchQueue.main.async {
|
||||
self.updateTimeValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func audioPlayer(failedWithError error: Error?) {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(seekTo seconds: Int, didFinish: Bool) {
|
||||
func handleAudioPlayerDidSeek(data: AudioPlayer.SeekEventData) {
|
||||
isScrubbing = false
|
||||
}
|
||||
|
||||
func audioPlayer(didUpdateDuration duration: Double) {
|
||||
slider.maximumValue = Float(controller.player.duration)
|
||||
slider.setValue(Float(controller.player.currentTime), animated: true)
|
||||
|
||||
elapsedTimeLabel.text = controller.player.currentTime.secondsToString()
|
||||
remainingTimeLabel.text = (controller.player.duration - controller.player.currentTime).secondsToString()
|
||||
func handleAudioPlayerUpdateDuration(data: AudioPlayer.UpdateDurationEventData) {
|
||||
DispatchQueue.main.async {
|
||||
self.updateTimeValues()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import AVFoundation
|
||||
|
||||
class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
|
||||
|
||||
var status: AVPlayerStatus?
|
||||
var timeControlStatus: AVPlayerTimeControlStatus?
|
||||
var status: AVPlayer.Status?
|
||||
var timeControlStatus: AVPlayer.TimeControlStatus?
|
||||
|
||||
override func spec() {
|
||||
|
||||
@@ -19,6 +19,7 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
|
||||
|
||||
beforeEach {
|
||||
player = AVPlayer()
|
||||
player.volume = 0.0
|
||||
observer = AVPlayerObserver(player: player)
|
||||
observer.delegate = self
|
||||
}
|
||||
@@ -58,11 +59,11 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func player(statusDidChange status: AVPlayerStatus) {
|
||||
func player(statusDidChange status: AVPlayer.Status) {
|
||||
self.status = status
|
||||
}
|
||||
|
||||
func player(didChangeTimeControlStatus status: AVPlayerTimeControlStatus) {
|
||||
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus) {
|
||||
self.timeControlStatus = status
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
@@ -13,51 +14,12 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
var wrapper: AVPlayerWrapper!
|
||||
|
||||
beforeEach {
|
||||
wrapper = AVPlayerWrapper()
|
||||
wrapper.automaticallyWaitsToMinimizeStalling = false
|
||||
let player = AVPlayer()
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
player.volume = 0.0
|
||||
wrapper = AVPlayerWrapper(avPlayer: player)
|
||||
wrapper.bufferDuration = 0.0001
|
||||
wrapper.volume = 0.0
|
||||
}
|
||||
|
||||
context("when calling play() with no item", {
|
||||
var err: APError.PlaybackError?
|
||||
|
||||
beforeEach {
|
||||
do {
|
||||
try wrapper.play()
|
||||
}
|
||||
catch {
|
||||
if let error = error as? APError.PlaybackError {
|
||||
err = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it("should throw a noItemLoaded error", closure: {
|
||||
expect(err).toNot(beNil())
|
||||
expect(err).to(equal(APError.PlaybackError.noLoadedItem))
|
||||
})
|
||||
})
|
||||
|
||||
context("when calling pause() with no item", {
|
||||
var err: APError.PlaybackError?
|
||||
|
||||
beforeEach {
|
||||
do {
|
||||
try wrapper.pause()
|
||||
}
|
||||
catch {
|
||||
if let error = error as? APError.PlaybackError {
|
||||
err = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it("should throw a noItemLoaded error", closure: {
|
||||
expect(err).toNot(beNil())
|
||||
expect(err).to(equal(APError.PlaybackError.noLoadedItem))
|
||||
})
|
||||
})
|
||||
|
||||
describe("its state", {
|
||||
it("should be idle", closure: {
|
||||
@@ -66,12 +28,8 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
|
||||
context("when loading a source", {
|
||||
beforeEach {
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
|
||||
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should be loading", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.loading))
|
||||
})
|
||||
|
||||
it("should eventually be ready", closure: {
|
||||
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.ready))
|
||||
@@ -80,7 +38,7 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
|
||||
context("when playing with no source", {
|
||||
beforeEach {
|
||||
try? wrapper.play()
|
||||
wrapper.play()
|
||||
}
|
||||
it("should be idle", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
|
||||
@@ -89,7 +47,7 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
|
||||
context("when playing a source", {
|
||||
beforeEach {
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
|
||||
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be playing", closure: {
|
||||
@@ -106,10 +64,10 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
wrapper.delegate = holder
|
||||
holder.stateUpdate = { (state) in
|
||||
if state == .playing {
|
||||
try? wrapper.pause()
|
||||
wrapper.pause()
|
||||
}
|
||||
}
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
|
||||
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be paused", closure: {
|
||||
@@ -123,10 +81,10 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
wrapper.delegate = holder
|
||||
holder.stateUpdate = { (state) in
|
||||
if state == .playing {
|
||||
try? wrapper.togglePlaying()
|
||||
wrapper.togglePlaying()
|
||||
}
|
||||
}
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
|
||||
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
|
||||
}
|
||||
it("should eventually be paused", closure: {
|
||||
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.paused))
|
||||
@@ -149,7 +107,7 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
receivedIdleUpdate = true
|
||||
}
|
||||
}
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: true)
|
||||
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be 'idle'", closure: {
|
||||
@@ -160,12 +118,23 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
|
||||
context("when seeking before loading", {
|
||||
beforeEach {
|
||||
try? wrapper.seek(to: 10)
|
||||
wrapper.seek(to: 10)
|
||||
}
|
||||
it("should be idle", closure: {
|
||||
expect(wrapper.state).to(equal(AVPlayerWrapperState.idle))
|
||||
})
|
||||
})
|
||||
|
||||
context("when loading source with initial time", closure: {
|
||||
let initialTime: TimeInterval = 4.0
|
||||
beforeEach {
|
||||
wrapper.load(from: LongSource.url, playWhenReady: true, initialTime: initialTime)
|
||||
}
|
||||
|
||||
it("should eventually be playing", closure: {
|
||||
expect(wrapper.state).toEventually(equal(AVPlayerWrapperState.playing))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("its duration", {
|
||||
@@ -175,7 +144,7 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
|
||||
context("when loading source", {
|
||||
beforeEach {
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
|
||||
wrapper.load(from: URL(fileURLWithPath: LongSource.path), playWhenReady: false)
|
||||
}
|
||||
it("should eventually not be 0", closure: {
|
||||
expect(wrapper.duration).toEventuallyNot(equal(0))
|
||||
@@ -193,18 +162,70 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
let seekTime: TimeInterval = 0.5
|
||||
beforeEach {
|
||||
wrapper.delegate = holder
|
||||
holder.stateUpdate = { (state) in
|
||||
if state == .ready && wrapper.duration != 0 {
|
||||
try? wrapper.seek(to: seekTime)
|
||||
}
|
||||
}
|
||||
try? wrapper.load(fromFilePath: Source.path, playWhenReady: false)
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wrapper.seek(to: seekTime)
|
||||
}
|
||||
|
||||
it("should eventually be equal to the seeked time", closure: {
|
||||
expect(wrapper.currentTime).toEventually(equal(seekTime))
|
||||
})
|
||||
})
|
||||
|
||||
context("when playing from initial time", closure: {
|
||||
let initialTime: TimeInterval = 4.0
|
||||
beforeEach {
|
||||
wrapper.load(from: LongSource.url, playWhenReady: false, initialTime: initialTime)
|
||||
}
|
||||
|
||||
it("should eventuallt be equal to the initial time", closure: {
|
||||
expect(wrapper.currentTime).toEventually(equal(initialTime))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("its rate", {
|
||||
it("should be 0", closure: {
|
||||
expect(wrapper.rate).to(equal(0.0))
|
||||
})
|
||||
|
||||
context("when playing a source", {
|
||||
beforeEach {
|
||||
wrapper.load(from: URL(fileURLWithPath: Source.path), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be 1.0", closure: {
|
||||
expect(wrapper.rate).toEventually(equal(1.0))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe("its automaticallyWaitsToMinimizeStalling option", {
|
||||
it("should be false", closure: {
|
||||
expect(wrapper.automaticallyWaitsToMinimizeStalling).to(beFalse())
|
||||
})
|
||||
|
||||
context("when setting it to true", {
|
||||
beforeEach {
|
||||
wrapper.automaticallyWaitsToMinimizeStalling = true
|
||||
}
|
||||
|
||||
it("should be true", closure: {
|
||||
expect(wrapper.automaticallyWaitsToMinimizeStalling).to(beTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("its timeEventFrequency", {
|
||||
context("when updated", {
|
||||
beforeEach {
|
||||
wrapper.timeEventFrequency = .everyHalfSecond
|
||||
}
|
||||
|
||||
it("should update the playerTimeObservers periodicObserverTimeInterval", closure: {
|
||||
expect(wrapper.playerTimeObserver.periodicObserverTimeInterval).to(equal(TimeEventFrequency.everyHalfSecond.getTime()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
@@ -214,12 +235,12 @@ class AVPlayerWrapperTests: QuickSpec {
|
||||
}
|
||||
|
||||
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
|
||||
|
||||
func AVWrapperItemDidPlayToEndTime() {
|
||||
|
||||
}
|
||||
|
||||
var state: AVPlayerWrapperState? {
|
||||
didSet {
|
||||
print(state)
|
||||
if let state = state {
|
||||
self.stateUpdate?(state)
|
||||
}
|
||||
@@ -233,10 +254,6 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
func AVWrapperItemDidComplete() {
|
||||
|
||||
}
|
||||
|
||||
func AVWrapper(secondsElapsed seconds: Double) {
|
||||
|
||||
}
|
||||
@@ -245,8 +262,9 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
|
||||
}
|
||||
|
||||
var seekCompletion: (() -> Void)?
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool) {
|
||||
|
||||
seekCompletion?()
|
||||
}
|
||||
|
||||
func AVWrapper(didUpdateDuration duration: Double) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import MediaPlayer
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
class AudioPlayerEventTests: QuickSpec {
|
||||
|
||||
class EventListener {
|
||||
var handleEvent: ((Void)) -> Void = { _ in
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("An event") {
|
||||
var event: AudioPlayer.Event<(Void)>!
|
||||
beforeEach {
|
||||
event = AudioPlayer.Event()
|
||||
}
|
||||
|
||||
describe("its invokers", {
|
||||
|
||||
context("when adding a listener", {
|
||||
var listener: EventListener!
|
||||
beforeEach {
|
||||
listener = EventListener()
|
||||
event.addListener(listener, listener!.handleEvent)
|
||||
}
|
||||
|
||||
it("should have one element", closure: {
|
||||
expect(event.invokers.count).toEventuallyNot(equal(0))
|
||||
})
|
||||
|
||||
context("then that listener is deinitialized and an an event is emitted", {
|
||||
beforeEach {
|
||||
listener = nil
|
||||
event.emit(data: ())
|
||||
}
|
||||
|
||||
it("should remove the invoker", closure: {
|
||||
expect(event.invokers.count).toEventually(equal(0))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
context("when adding multiple listeners", {
|
||||
var listeners: [EventListener]!
|
||||
|
||||
beforeEach {
|
||||
listeners = [0..<15].map {_ in
|
||||
let listener = EventListener()
|
||||
event.addListener(listener, listener.handleEvent)
|
||||
return listener
|
||||
}
|
||||
}
|
||||
|
||||
it("should have several listeners", closure: {
|
||||
expect(event.invokers.count).toEventually(equal(listeners.count))
|
||||
})
|
||||
|
||||
context("then removing one", {
|
||||
beforeEach {
|
||||
event.removeListener(listeners[listeners.count / 2])
|
||||
}
|
||||
|
||||
it("should have one less invoker", closure: {
|
||||
expect(event.invokers.count).toEventually(equal(listeners.count - 1))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
@@ -11,9 +12,9 @@ class AudioPlayerTests: QuickSpec {
|
||||
|
||||
beforeEach {
|
||||
audioPlayer = AudioPlayer()
|
||||
audioPlayer.automaticallyWaitsToMinimizeStalling = false
|
||||
audioPlayer.bufferDuration = 0.0001
|
||||
audioPlayer.volume = 0
|
||||
audioPlayer.automaticallyWaitsToMinimizeStalling = false
|
||||
audioPlayer.volume = 0.0
|
||||
}
|
||||
|
||||
describe("its state", {
|
||||
@@ -24,7 +25,7 @@ class AudioPlayerTests: QuickSpec {
|
||||
|
||||
context("when audio item is loaded", {
|
||||
beforeEach {
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
}
|
||||
|
||||
it("it should eventually be ready", closure: {
|
||||
@@ -34,7 +35,7 @@ class AudioPlayerTests: QuickSpec {
|
||||
|
||||
context("when an item is loaded (playWhenReady=true)", {
|
||||
beforeEach {
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("it should eventually be playing", closure: {
|
||||
@@ -43,17 +44,15 @@ class AudioPlayerTests: QuickSpec {
|
||||
})
|
||||
|
||||
context("when playing an item", {
|
||||
var holder: AudioPlayerDelegateHolder!
|
||||
var listener: AudioPlayerEventListener!
|
||||
beforeEach {
|
||||
holder = AudioPlayerDelegateHolder()
|
||||
audioPlayer.delegate = holder
|
||||
holder.stateUpdate = { state in
|
||||
print(state.rawValue)
|
||||
listener = AudioPlayerEventListener(audioPlayer: audioPlayer)
|
||||
listener.stateUpdate = { state in
|
||||
if state == .ready {
|
||||
try? audioPlayer.play()
|
||||
audioPlayer.play()
|
||||
}
|
||||
}
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should eventually be playing", closure: {
|
||||
@@ -62,16 +61,15 @@ class AudioPlayerTests: QuickSpec {
|
||||
})
|
||||
|
||||
context("when pausing an item", {
|
||||
var holder: AudioPlayerDelegateHolder!
|
||||
var listener: AudioPlayerEventListener!
|
||||
beforeEach {
|
||||
holder = AudioPlayerDelegateHolder()
|
||||
audioPlayer.delegate = holder
|
||||
holder.stateUpdate = { (state) in
|
||||
listener = AudioPlayerEventListener(audioPlayer: audioPlayer)
|
||||
listener.stateUpdate = { (state) in
|
||||
if state == .playing {
|
||||
try? audioPlayer.pause()
|
||||
audioPlayer.pause()
|
||||
}
|
||||
}
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be paused", closure: {
|
||||
@@ -80,16 +78,15 @@ class AudioPlayerTests: QuickSpec {
|
||||
})
|
||||
|
||||
context("when stopping an item", {
|
||||
var holder: AudioPlayerDelegateHolder!
|
||||
var listener: AudioPlayerEventListener!
|
||||
beforeEach {
|
||||
holder = AudioPlayerDelegateHolder()
|
||||
audioPlayer.delegate = holder
|
||||
holder.stateUpdate = { (state) in
|
||||
listener = AudioPlayerEventListener(audioPlayer: audioPlayer)
|
||||
listener.stateUpdate = { (state) in
|
||||
if state == .playing {
|
||||
audioPlayer.stop()
|
||||
}
|
||||
}
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be idle", closure: {
|
||||
@@ -105,22 +102,28 @@ class AudioPlayerTests: QuickSpec {
|
||||
})
|
||||
|
||||
context("when seeking to a time", {
|
||||
let holder = AudioPlayerDelegateHolder()
|
||||
let seekTime: TimeInterval = 0.5
|
||||
let seekTime: TimeInterval = 1.0
|
||||
beforeEach {
|
||||
audioPlayer.delegate = holder
|
||||
holder.stateUpdate = { (state) in
|
||||
if state == .ready && audioPlayer.duration != 0 {
|
||||
try? audioPlayer.seek(to: seekTime)
|
||||
}
|
||||
}
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: false)
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
audioPlayer.seek(to: seekTime)
|
||||
}
|
||||
|
||||
it("should eventually be equal to the seeked time", closure: {
|
||||
expect(audioPlayer.currentTime).toEventually(equal(seekTime))
|
||||
})
|
||||
})
|
||||
|
||||
context("when playing an item with an initial time", {
|
||||
var item: DefaultAudioItemInitialTime!
|
||||
beforeEach {
|
||||
item = DefaultAudioItemInitialTime(audioUrl: LongSource.path, artist: nil, title: nil, albumTitle: nil, sourceType: .file, artwork: nil, initialTime: 4.0)
|
||||
try? audioPlayer.load(item: item, playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should eventaully be equal to the initial time", closure: {
|
||||
expect(audioPlayer.currentTime).toEventually(equal(item.getInitialTime()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("its rate", {
|
||||
@@ -130,7 +133,7 @@ class AudioPlayerTests: QuickSpec {
|
||||
|
||||
context("when playing an item", {
|
||||
beforeEach {
|
||||
try? audioPlayer.loadItem(Source.getAudioItem(), playWhenReady: true)
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be 1.0", closure: {
|
||||
@@ -138,14 +141,57 @@ class AudioPlayerTests: QuickSpec {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("its currentItem", {
|
||||
it("should be nil", closure: {
|
||||
expect(audioPlayer.currentItem).to(beNil())
|
||||
})
|
||||
|
||||
context("when loading an item", {
|
||||
beforeEach {
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should not be nil", closure: {
|
||||
expect(audioPlayer.currentItem).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("when setting the timePitchAlgorithm", {
|
||||
|
||||
beforeEach {
|
||||
audioPlayer.audioTimePitchAlgorithm = .timeDomain
|
||||
}
|
||||
|
||||
context("then loading an item", {
|
||||
beforeEach {
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should have the applied timePitchAlgorithm", closure: {
|
||||
expect(audioPlayer.wrapper.currentItem?.audioTimePitchAlgorithm).to(equal(AVAudioTimePitchAlgorithm.timeDomain))
|
||||
})
|
||||
})
|
||||
|
||||
context("then loading a timepitching item", {
|
||||
beforeEach {
|
||||
let item = DefaultAudioItemTimePitching(audioUrl: Source.path, artist: nil, title: nil, albumTitle: nil, sourceType: .file, artwork: nil, audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm.spectral)
|
||||
try? audioPlayer.load(item: item, playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should have the applied timePitchAlgorithm", closure: {
|
||||
expect(audioPlayer.wrapper.currentItem?.audioTimePitchAlgorithm).to(equal(AVAudioTimePitchAlgorithm.spectral))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AudioPlayerDelegateHolder: AudioPlayerDelegate {
|
||||
class AudioPlayerEventListener {
|
||||
|
||||
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
|
||||
var state: AudioPlayerState? {
|
||||
didSet {
|
||||
if let state = state {
|
||||
@@ -154,32 +200,20 @@ class AudioPlayerDelegateHolder: AudioPlayerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func audioPlayer(playerDidChangeState state: AudioPlayerState) {
|
||||
var stateUpdate: ((_ state: AudioPlayerState) -> Void)?
|
||||
var seekCompletion: (() -> Void)?
|
||||
|
||||
init(audioPlayer: AudioPlayer) {
|
||||
audioPlayer.event.stateChange.addListener(self, handleDidUpdateState)
|
||||
audioPlayer.event.seek.addListener(self, handleSeek)
|
||||
}
|
||||
|
||||
func handleDidUpdateState(state: AudioPlayerState) {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
func audioPlayerItemDidComplete() {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(secondsElapsed seconds: Double) {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(failedWithError error: Error?) {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(seekTo seconds: Int, didFinish: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func audioPlayer(didUpdateDuration duration: Double) {
|
||||
if let state = self.state {
|
||||
self.stateUpdate?(state)
|
||||
}
|
||||
func handleSeek(data: AudioPlayer.SeekEventData) {
|
||||
seekCompletion?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class AudioSessionControllerTests: QuickSpec {
|
||||
override func spec() {
|
||||
|
||||
describe("An AudioSessionController") {
|
||||
let audioSessionController: AudioSessionController = AudioSessionController.shared
|
||||
let audioSessionController: AudioSessionController = AudioSessionController(audioSession: NonFailingAudioSession())
|
||||
|
||||
it("should be inactive", closure: {
|
||||
expect(audioSessionController.audioSessionIsActive).to(beFalse())
|
||||
@@ -55,7 +55,7 @@ class AudioSessionControllerTests: QuickSpec {
|
||||
context("when a interruption arrives", {
|
||||
var delegate: AudioSessionControllerDelegateImplementation!
|
||||
beforeEach {
|
||||
let notification = Notification(name: .AVAudioSessionInterruption, object: nil, userInfo: [
|
||||
let notification = Notification(name: AVAudioSession.interruptionNotification, object: nil, userInfo: [
|
||||
AVAudioSessionInterruptionTypeKey: UInt(0)
|
||||
])
|
||||
delegate = AudioSessionControllerDelegateImplementation()
|
||||
@@ -69,18 +69,32 @@ class AudioSessionControllerTests: QuickSpec {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
describe("An AudioSessionController with a failing AudioSession") {
|
||||
var audioSessionController: AudioSessionController!
|
||||
beforeEach {
|
||||
audioSessionController = AudioSessionController(audioSession: FailingAudioSession())
|
||||
}
|
||||
|
||||
context("when activated", {
|
||||
beforeEach {
|
||||
try? audioSessionController.activateSession()
|
||||
}
|
||||
|
||||
it("should be inactive", closure: {
|
||||
expect(audioSessionController.audioSessionIsActive).to(beFalse())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelegate {
|
||||
|
||||
var interruptionType: AVAudioSessionInterruptionType? = nil
|
||||
var interruptionType: AVAudioSession.InterruptionType? = nil
|
||||
|
||||
func handleInterruption(type: AVAudioSessionInterruptionType) {
|
||||
func handleInterruption(type: AVAudioSession.InterruptionType) {
|
||||
self.interruptionType = type
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// AudioSession.swift
|
||||
// SwiftAudio_Tests
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 31/10/2018.
|
||||
// Copyright © 2018 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
|
||||
class NonFailingAudioSession: AudioSession {
|
||||
|
||||
var category: AVAudioSession.Category = AVAudioSession.Category.playback
|
||||
|
||||
var mode: AVAudioSession.Mode = AVAudioSession.Mode.default
|
||||
|
||||
var categoryOptions: AVAudioSession.CategoryOptions = []
|
||||
|
||||
var availableCategories: [AVAudioSession.Category] = []
|
||||
|
||||
var isOtherAudioPlaying: Bool = false
|
||||
|
||||
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) throws {}
|
||||
|
||||
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, policy: AVAudioSession.RouteSharingPolicy, options: AVAudioSession.CategoryOptions) throws {}
|
||||
|
||||
func setActive(_ active: Bool) throws {}
|
||||
|
||||
func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws {}
|
||||
|
||||
}
|
||||
|
||||
class FailingAudioSession: AudioSession {
|
||||
|
||||
var category: AVAudioSession.Category = AVAudioSession.Category.playback
|
||||
|
||||
var mode: AVAudioSession.Mode = AVAudioSession.Mode.default
|
||||
|
||||
var categoryOptions: AVAudioSession.CategoryOptions = AVAudioSession.CategoryOptions.allowBluetooth
|
||||
|
||||
var availableCategories: [AVAudioSession.Category] = []
|
||||
|
||||
var isOtherAudioPlaying: Bool = false
|
||||
|
||||
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) throws {
|
||||
throw AVError(AVError.unknown)
|
||||
}
|
||||
|
||||
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, policy: AVAudioSession.RouteSharingPolicy, options: AVAudioSession.CategoryOptions) throws {
|
||||
throw AVError(AVError.unknown)
|
||||
}
|
||||
|
||||
func setActive(_ active: Bool) throws {
|
||||
throw AVError(AVError.unknown)
|
||||
}
|
||||
|
||||
func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws {
|
||||
throw AVError(AVError.unknown)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// NowPlayingInfoCenter.swift
|
||||
// SwiftAudio_Tests
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 03/03/2019.
|
||||
// Copyright © 2019 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
class NowPlayingInfoCenter_Mock: NowPlayingInfoCenter {
|
||||
|
||||
var nowPlayingInfo: [String : Any]? = nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// NowPlayingInfoController.swift
|
||||
// SwiftAudio_Tests
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 03/03/2019.
|
||||
// Copyright © 2019 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
class NowPlayingInfoController_Mock: NowPlayingInfoControllerProtocol {
|
||||
|
||||
var info: [String: Any] = [:]
|
||||
|
||||
required public init() {
|
||||
}
|
||||
|
||||
required public init(infoCenter: NowPlayingInfoCenter) {
|
||||
}
|
||||
|
||||
public func set(keyValues: [NowPlayingInfoKeyValue]) {
|
||||
keyValues.forEach { (keyValue) in
|
||||
info[keyValue.getKey()] = keyValue.getValue()
|
||||
}
|
||||
}
|
||||
|
||||
public func set(keyValue: NowPlayingInfoKeyValue) {
|
||||
info[keyValue.getKey()] = keyValue.getValue()
|
||||
}
|
||||
|
||||
func getTitle() -> String? {
|
||||
return info[MediaItemProperty.title(nil).getKey()] as? String
|
||||
}
|
||||
|
||||
func getArtist() -> String? {
|
||||
return info[MediaItemProperty.artist(nil).getKey()] as? String
|
||||
}
|
||||
|
||||
func getAlbumTitle() -> String? {
|
||||
return info[MediaItemProperty.albumTitle(nil).getKey()] as? String
|
||||
}
|
||||
|
||||
func getRate() -> Double? {
|
||||
return info[NowPlayingInfoProperty.playbackRate(nil).getKey()] as? Double
|
||||
}
|
||||
|
||||
func getDuration() -> Double? {
|
||||
return info[MediaItemProperty.duration(nil).getKey()] as? Double
|
||||
}
|
||||
|
||||
func getCurrentTime() -> Double? {
|
||||
return info[NowPlayingInfoProperty.elapsedPlaybackTime(nil).getKey()] as? Double
|
||||
}
|
||||
|
||||
func getArtwork() -> MPMediaItemArtwork? {
|
||||
return info[MediaItemProperty.artwork(nil).getKey()] as? MPMediaItemArtwork
|
||||
}
|
||||
|
||||
func clear() {
|
||||
info = [:]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import MediaPlayer
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
class NowPlayingInfoControllerTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
describe("An NowPlayingInfoController") {
|
||||
|
||||
var nowPlayingController: NowPlayingInfoController!
|
||||
|
||||
beforeEach {
|
||||
nowPlayingController = NowPlayingInfoController(infoCenter: NowPlayingInfoCenter_Mock())
|
||||
}
|
||||
|
||||
describe("its info dictionary") {
|
||||
|
||||
context("when setting a value") {
|
||||
beforeEach {
|
||||
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
|
||||
}
|
||||
|
||||
it("should not be empty") {
|
||||
expect(nowPlayingController.info.count).toNot(equal(0))
|
||||
}
|
||||
|
||||
context("then calling clear()") {
|
||||
beforeEach {
|
||||
nowPlayingController.clear()
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
expect(nowPlayingController.info.count).to(equal(0))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("its info center") {
|
||||
|
||||
context("when setting a value") {
|
||||
|
||||
beforeEach {
|
||||
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
|
||||
}
|
||||
|
||||
it("should not be nil") {
|
||||
expect(nowPlayingController.infoCenter.nowPlayingInfo).toNot(beNil())
|
||||
}
|
||||
|
||||
it("should not be empty") {
|
||||
expect(nowPlayingController.infoCenter.nowPlayingInfo?.count).toNot(equal(0))
|
||||
}
|
||||
|
||||
context("then calling clear()") {
|
||||
|
||||
beforeEach {
|
||||
nowPlayingController.clear()
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
expect(nowPlayingController.infoCenter.nowPlayingInfo?.count).to(equal(0))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import MediaPlayer
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
/// Tests that the AudioPlayer is automatically updating the values it should update in the NowPlayingInfoController.
|
||||
class NowPlayingInfoTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("An AudioPlayer") {
|
||||
|
||||
var audioPlayer: AudioPlayer!
|
||||
var nowPlayingController: NowPlayingInfoController_Mock!
|
||||
|
||||
beforeEach {
|
||||
nowPlayingController = NowPlayingInfoController_Mock()
|
||||
audioPlayer = AudioPlayer(nowPlayingInfoController: nowPlayingController)
|
||||
audioPlayer.automaticallyUpdateNowPlayingInfo = true
|
||||
audioPlayer.volume = 0
|
||||
}
|
||||
|
||||
describe("its NowPlayingInfoController", {
|
||||
|
||||
context("when loading an AudioItem", {
|
||||
|
||||
var item: AudioItem!
|
||||
|
||||
beforeEach {
|
||||
item = Source.getAudioItem()
|
||||
try? audioPlayer.load(item: item, playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should eventually be updated with meta data", closure: {
|
||||
expect(nowPlayingController.getTitle()).toEventuallyNot(beNil())
|
||||
expect(nowPlayingController.getTitle()).toEventually(equal(item.getTitle()!))
|
||||
|
||||
expect(nowPlayingController.getArtist()).toEventuallyNot(beNil())
|
||||
expect(nowPlayingController.getArtist()).toEventually(equal(item.getArtist()!))
|
||||
|
||||
expect(nowPlayingController.getAlbumTitle()).toEventuallyNot(beNil())
|
||||
expect(nowPlayingController.getAlbumTitle()).toEventually(equal(item.getAlbumTitle()!))
|
||||
|
||||
expect(nowPlayingController.getArtwork()).toEventuallyNot(beNil())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
context("when playing an AudioItem", {
|
||||
|
||||
var item: AudioItem!
|
||||
|
||||
beforeEach {
|
||||
item = LongSource.getAudioItem()
|
||||
try? audioPlayer.load(item: item, playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be updated with playback values", closure: {
|
||||
expect(nowPlayingController.getRate()).toEventuallyNot(beNil())
|
||||
expect(nowPlayingController.getDuration()).toEventuallyNot(beNil())
|
||||
expect(nowPlayingController.getCurrentTime()).toEventuallyNot(beNil())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,57 @@ class QueueManagerTests: QuickSpec {
|
||||
manager = QueueManager()
|
||||
}
|
||||
|
||||
describe("its current item", {
|
||||
|
||||
it("should be nil", closure: {
|
||||
expect(manager.current).to(beNil())
|
||||
})
|
||||
|
||||
context("when one item is added", closure: {
|
||||
beforeEach {
|
||||
manager.addItem(self.dummyItem)
|
||||
}
|
||||
|
||||
it("should not be nil", closure: {
|
||||
expect(manager.current).toNot(beNil())
|
||||
})
|
||||
|
||||
it("should be the added item", closure: {
|
||||
expect(manager.current).to(equal(self.dummyItem))
|
||||
})
|
||||
|
||||
context("then replaced", closure: {
|
||||
beforeEach {
|
||||
manager.replaceCurrentItem(with: 1)
|
||||
}
|
||||
it("should be the new item", closure: {
|
||||
expect(manager.current).to(equal(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context("when replaced", closure: {
|
||||
beforeEach {
|
||||
manager.replaceCurrentItem(with: 1)
|
||||
}
|
||||
|
||||
it("should not be nil", closure: {
|
||||
expect(manager.current).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
context("when mulitple items are added", {
|
||||
beforeEach {
|
||||
manager.addItems(self.dummyItems)
|
||||
}
|
||||
|
||||
it("should not be nil", closure: {
|
||||
expect(manager.current).toNot(beNil())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
context("when adding one item", {
|
||||
|
||||
beforeEach {
|
||||
@@ -30,9 +81,13 @@ class QueueManagerTests: QuickSpec {
|
||||
expect(manager.items).notTo(beEmpty())
|
||||
})
|
||||
|
||||
it("should set it as the current item", closure: {
|
||||
expect(manager.current).toNot(beNil())
|
||||
expect(manager.current).to(equal(self.dummyItem))
|
||||
context("then replacing the item", closure: {
|
||||
beforeEach {
|
||||
manager.replaceCurrentItem(with: 1)
|
||||
}
|
||||
it("should have replaced the current item", closure: {
|
||||
expect(manager.current).to(equal(1))
|
||||
})
|
||||
})
|
||||
|
||||
context("then calling next", {
|
||||
@@ -115,14 +170,72 @@ class QueueManagerTests: QuickSpec {
|
||||
expect(manager.current).to(equal(self.dummyItems.first))
|
||||
})
|
||||
})
|
||||
|
||||
context("then removing previous items", {
|
||||
beforeEach {
|
||||
manager.removePreviousItems()
|
||||
}
|
||||
it("should have no previous items", closure: {
|
||||
expect(manager.previousItems.count).to(equal(0))
|
||||
})
|
||||
it("should have current index zero", closure: {
|
||||
expect(manager.currentIndex).to(equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context("adding more items", {
|
||||
var initialItemCount: Int!
|
||||
let newItems: [Int] = [10, 11, 12, 13]
|
||||
beforeEach {
|
||||
initialItemCount = manager.items.count
|
||||
try? manager.addItems(newItems, at: manager.items.endIndex - 1)
|
||||
}
|
||||
|
||||
it("should have more items", closure: {
|
||||
expect(manager.items.count).to(equal(initialItemCount + newItems.count))
|
||||
})
|
||||
})
|
||||
|
||||
context("adding more items at a smaller index than currentIndex", {
|
||||
var initialCurrentIndex: Int!
|
||||
let newItems: [Int] = [10, 11, 12, 13]
|
||||
beforeEach {
|
||||
initialCurrentIndex = manager.currentIndex
|
||||
try? manager.addItems(newItems, at: initialCurrentIndex)
|
||||
}
|
||||
|
||||
it("currentIndex should increase by number of new items", closure: {
|
||||
expect(manager.currentIndex).to(equal(initialCurrentIndex + newItems.count))
|
||||
})
|
||||
})
|
||||
|
||||
// MARK: - Removal
|
||||
|
||||
context("then removing a item with index less than currentIndex", {
|
||||
beforeEach {
|
||||
var removed: Int?
|
||||
var initialCurrentIndex: Int!
|
||||
beforeEach {
|
||||
let _ = try? manager.jump(to: 3)
|
||||
initialCurrentIndex = manager.currentIndex
|
||||
removed = try? manager.removeItem(at: initialCurrentIndex - 1)
|
||||
}
|
||||
|
||||
it("should remove an item", closure: {
|
||||
expect(removed).toNot(beNil())
|
||||
})
|
||||
|
||||
it("should decrement the currentIndex", closure: {
|
||||
expect(manager.currentIndex).to(equal(initialCurrentIndex - 1))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
context("then removing the second item", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: 1)
|
||||
removed = try? manager.removeItem(at: 1)
|
||||
}
|
||||
|
||||
it("should have one less item", closure: {
|
||||
@@ -134,7 +247,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing the last item", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: self.dummyItems.count - 1)
|
||||
removed = try? manager.removeItem(at: self.dummyItems.count - 1)
|
||||
}
|
||||
|
||||
it("should have one less item", closure: {
|
||||
@@ -146,7 +259,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing the current item", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: manager.currentIndex)
|
||||
removed = try? manager.removeItem(at: manager.currentIndex)
|
||||
}
|
||||
it("should not remove any items", closure: {
|
||||
expect(removed).to(beNil())
|
||||
@@ -157,7 +270,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing with too large index", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: self.dummyItems.count)
|
||||
removed = try? manager.removeItem(at: self.dummyItems.count)
|
||||
}
|
||||
|
||||
it("should not remove any items", closure: {
|
||||
@@ -169,7 +282,7 @@ class QueueManagerTests: QuickSpec {
|
||||
context("then removing with too small index", {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? manager.remove(atIndex: -1)
|
||||
removed = try? manager.removeItem(at: -1)
|
||||
}
|
||||
|
||||
it("should not remove any items", closure: {
|
||||
@@ -178,6 +291,16 @@ class QueueManagerTests: QuickSpec {
|
||||
})
|
||||
})
|
||||
|
||||
context("then removing upcoming items", {
|
||||
beforeEach {
|
||||
manager.removeUpcomingItems()
|
||||
}
|
||||
|
||||
it("should have no next items", closure: {
|
||||
expect(manager.nextItems.count).to(equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
// MARK: - Jumping
|
||||
|
||||
context("then jumping to the current item", {
|
||||
@@ -343,6 +466,22 @@ class QueueManagerTests: QuickSpec {
|
||||
expect(manager.items).to(equal(afterMoving))
|
||||
})
|
||||
})
|
||||
|
||||
// MARK: - Clear
|
||||
|
||||
context("when queue is cleared", {
|
||||
beforeEach {
|
||||
manager.clearQueue()
|
||||
}
|
||||
|
||||
it("should have currentIndex 0", closure: {
|
||||
expect(manager.currentIndex).to(equal(0))
|
||||
})
|
||||
|
||||
it("should have no items", closure: {
|
||||
expect(manager.items.count).to(equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
var audioPlayer: QueuedAudioPlayer!
|
||||
beforeEach {
|
||||
audioPlayer = QueuedAudioPlayer()
|
||||
audioPlayer.automaticallyWaitsToMinimizeStalling = false
|
||||
audioPlayer.bufferDuration = 0.0001
|
||||
audioPlayer.volume = 0
|
||||
audioPlayer.automaticallyWaitsToMinimizeStalling = false
|
||||
audioPlayer.volume = 0.0
|
||||
}
|
||||
describe("its current item", {
|
||||
it("should be nil", closure: {
|
||||
@@ -19,12 +19,24 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
})
|
||||
|
||||
context("when adding one item", {
|
||||
var item: AudioItem!
|
||||
beforeEach {
|
||||
try? audioPlayer.add(item: ShortSource.getAudioItem(), playWhenReady: false)
|
||||
item = ShortSource.getAudioItem()
|
||||
try? audioPlayer.add(item: item, playWhenReady: false)
|
||||
}
|
||||
it("should not be nil", closure: {
|
||||
expect(audioPlayer.currentItem).toNot(beNil())
|
||||
})
|
||||
|
||||
context("then loading a new item", closure: {
|
||||
beforeEach {
|
||||
try? audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should have replaced the item", closure: {
|
||||
expect(audioPlayer.currentItem?.getSourceUrl()).toNot(equal(item.getSourceUrl()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context("when adding multiple items", {
|
||||
@@ -70,7 +82,7 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
|
||||
context("then removing one item", {
|
||||
beforeEach {
|
||||
try? audioPlayer.removeItem(atIndex: 1)
|
||||
try? audioPlayer.removeItem(at: 1)
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
@@ -86,6 +98,26 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
expect(audioPlayer.nextItems.count).to(equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
context("then removing upcoming items", {
|
||||
beforeEach {
|
||||
audioPlayer.removeUpcomingItems()
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
expect(audioPlayer.nextItems.count).to(equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
context("then stopping", {
|
||||
beforeEach {
|
||||
audioPlayer.stop()
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
expect(audioPlayer.nextItems.count).to(equal(0))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -112,6 +144,26 @@ class QueuedAudioPlayerTests: QuickSpec {
|
||||
})
|
||||
})
|
||||
|
||||
context("then removing all previous items", {
|
||||
beforeEach {
|
||||
audioPlayer.removePreviousItems()
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
expect(audioPlayer.previousItems.count).to(equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
context("then stopping", {
|
||||
beforeEach {
|
||||
audioPlayer.stop()
|
||||
}
|
||||
|
||||
it("should be empty", closure: {
|
||||
expect(audioPlayer.previousItems.count).to(equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SwiftAudio
|
||||
|
||||
class SimpleAudioPlayerTests: QuickSpec {
|
||||
override func spec() {
|
||||
describe("A SimpleAudioPlayer") {
|
||||
var player: SimpleAudioPlayer!
|
||||
beforeEach {
|
||||
player = SimpleAudioPlayer()
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
player.bufferDuration = 0.0001
|
||||
player.volume = 0
|
||||
}
|
||||
|
||||
describe("its state", {
|
||||
it("should be idle", closure: {
|
||||
expect(player.playerState).to(equal(AudioPlayerState.idle))
|
||||
})
|
||||
|
||||
context("when loading an item with playeWhenReady: false", {
|
||||
beforeEach {
|
||||
try? player.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
}
|
||||
it("should eventually be ready", closure: {
|
||||
expect(player.playerState).toEventually(equal(AudioPlayerState.ready))
|
||||
})
|
||||
})
|
||||
|
||||
context("when loading an item with playWhenReady: true", {
|
||||
beforeEach {
|
||||
try? player.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
}
|
||||
it("should eventually be playing", closure: {
|
||||
expect(player.playerState).toEventually(equal(AudioPlayerState.playing))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,27 @@ import SwiftAudio
|
||||
|
||||
struct Source {
|
||||
static let path: String = Bundle.main.path(forResource: "TestSound", ofType: "m4a")!
|
||||
static let url: URL = URL(fileURLWithPath: Source.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
return DefaultAudioItem(audioUrl: Source.path, sourceType: .file)
|
||||
return DefaultAudioItem(audioUrl: Source.path, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .file, artwork: UIImage())
|
||||
}
|
||||
}
|
||||
|
||||
struct ShortSource {
|
||||
static let path: String = Bundle.main.path(forResource: "ShortTestSound", ofType: "m4a")!
|
||||
static let url: URL = URL(fileURLWithPath: ShortSource.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
return DefaultAudioItem(audioUrl: ShortSource.path, sourceType: .file)
|
||||
}
|
||||
}
|
||||
|
||||
struct LongSource {
|
||||
static let path: String = Bundle.main.path(forResource: "WAV-MP3", ofType: "wav")!
|
||||
static let url: URL = URL(fileURLWithPath: LongSource.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
return DefaultAudioItem(audioUrl: LongSource.path, sourceType: .file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SwiftAudio
|
||||
|
||||
[](https://travis-ci.org/jorgenhenrichsen/SwiftAudio)
|
||||
[](https://app.bitrise.io/app/3d3ac2ba8d817235)
|
||||
[](http://cocoapods.org/pods/SwiftAudio)
|
||||
[](https://codecov.io/gh/jorgenhenrichsen/SwiftAudio)
|
||||
[](http://cocoapods.org/pods/SwiftAudio)
|
||||
@@ -10,7 +10,7 @@ SwiftAudio is an audio player written in Swift, making it simpler to work with a
|
||||
|
||||
## Example
|
||||
|
||||
To see the audio player in action clone the repo and run the example project!
|
||||
To see the audio player in action, run the example project!
|
||||
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||
|
||||
## Requirements
|
||||
@@ -18,78 +18,127 @@ iOS 10.0+
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods
|
||||
SwiftAudio is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod 'SwiftAudio'
|
||||
pod 'SwiftAudio', '~> 0.7.0'
|
||||
```
|
||||
|
||||
### Carthage
|
||||
SwiftAudio supports [Carthage](https://github.com/Carthage/Carthage). Add this to your Cartfile:
|
||||
```ruby
|
||||
github "jorgenhenrichsen/SwiftAudio" ~> 0.7.0
|
||||
```
|
||||
Then follow the rest of Carthage instructions on [adding a framework](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application).
|
||||
|
||||
## Usage
|
||||
|
||||
### AudioPlayer
|
||||
To get started playing some audio:
|
||||
```swift
|
||||
let player = AudioPlayer()
|
||||
let audioItem = DefaultAudioItem(audioUrl: "someUrl", sourceType: .stream)
|
||||
player.load(item: audioItem, playWhenReady: true) // Load the item and start playing when the player is ready.
|
||||
```
|
||||
|
||||
To listen for events in the `AudioPlayer`, subscribe to events found in the `event` property of the `AudioPlayer`.
|
||||
To subscribe to an event:
|
||||
```swift
|
||||
class MyCustomViewController: UIViewController {
|
||||
|
||||
let audioPlayer = AudioPlayer()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
audioPlayer.event.stateChange.addListener(self, handleAudioPlayerStateChange)
|
||||
}
|
||||
|
||||
func handleAudioPlayerStateChange(state: AudioPlayerState) {
|
||||
// Handle the event
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### QueuedAudioPlayer
|
||||
The `QueuedAudioPlayer` is a subclass of `AudioPlayer` that maintains a queue of audio tracks.
|
||||
```swift
|
||||
let player = QueuedAudioPlayer()
|
||||
let audioItem = DefaultAudioItem(audioUrl: "someUrl", sourceType: .stream)
|
||||
player.add(item: audioItem)
|
||||
player.add(item: audioItem, playWhenReady: true) // Since this is the first item, we can supply playWhenReady: true to immedietaly start playing when the item is loaded.
|
||||
```
|
||||
|
||||
The player will load the track and start playing when ready. To disable this behaviour use `add(item:playWhenReady:)` and pass in `false`. This is `true` by default. To get notified of events during playback and loading, implement `AudioPlayerDelegate` and the player will notify you with changes.
|
||||
When a track is done playing, the player will load the next track and update the queue, as long as `automaticallyPlayNextSong` is `true` (default).
|
||||
|
||||
If you want a simpler audio player without queue functionality, use:
|
||||
##### Navigating the queue
|
||||
All `AudioItem`s are stored in either `previousItems` or `nextItems`, which refers to items that come prior to the `currentItem` and after, respectively. The queue is navigated with:
|
||||
```swift
|
||||
let player = SimpleAudioPlayer()
|
||||
let audioItem = DefaultAudioItem(audioUrl: "someUrl", sourceType: .stream)
|
||||
player.load(item: audioItem)
|
||||
player.next() // Increments the queue, and loads the next item.
|
||||
player.previous() // Decrements the queue, and loads the previous item.
|
||||
player.jumpToItem(atIndex:) // Jumps to a certain item and loads that item.
|
||||
```
|
||||
|
||||
**NOTE**: Do not use `AudioPlayer` directly. Use one of the above types.
|
||||
##### Manipulating the queue
|
||||
```swift
|
||||
player.removeItem(at:) // Remove a specific item from the queue.
|
||||
player.removeUpcomingItems() // Remove all items in nextItems.
|
||||
```
|
||||
|
||||
#### States
|
||||
The `AudioPlayer` has a `state` property, to make it easier to determine appropriate actions. The different states:
|
||||
+ **idle**: The player is doing nothing, no item is set as current. This is the default state.
|
||||
+ **ready**: The player has its current item set and is ready to start loading for playback. This is when you can call `play()` if you supplied `playWhenReady=false` when calling `load(item:playWhenReady)`.
|
||||
+ **loading**: The player is loading the track and will start playback soon.
|
||||
+ **playing**: The player is playing.
|
||||
+ **paused**: The player is paused.
|
||||
|
||||
#### Queue
|
||||
The `QueuedAudioPlayer` maintains a queue of audio tracks.
|
||||
The arrangement of the tracks are: [Previous]-[Current]-[Next].
|
||||
|
||||
When a track is done playing, the player will load the next track and update the queue, as long as `automaticallyPlayNextSong` is `true` (This is by default).
|
||||
|
||||
Items can be added to the queue by calling `player.add(item:)` or `player.add(items:)`.
|
||||
Use `removeItem(atIndex:)` and `moveItem(fromIndex:toIndex:)` to manipulate the queue.
|
||||
|
||||
The queue can be navigated by using `next()`, `previous()` and `jumpToItem(atIndex:)`
|
||||
### Configuring the AudioPlayer
|
||||
Current options for configuring the `AudioPlayer`:
|
||||
- `bufferDuration`: The amount of seconds to be buffered by the player.
|
||||
- `timeEventFrequency`: How often the player should call the delegate with time progress events.
|
||||
- `automaticallyWaitsToMinimizeStalling`: Indicates whether the player should automatically delay playback in order to minimize stalling.
|
||||
- `volume`
|
||||
- `isMuted`
|
||||
- `rate`
|
||||
- `audioTimePitchAlgorithm`: This value decides the `AVAudioTimePitchAlgorithm` used for each `AudioItem`. Implement `TimePitching` in your `AudioItem`-subclass to override individually for each `AudioItem`.
|
||||
|
||||
### Audio Session
|
||||
Remember to activate an audio session with an appropriate category for your app. This can be done with `AudioSessionCategory`:
|
||||
Remember to activate an audio session with an appropriate category for your app. This can be done with `AudioSessionController`:
|
||||
```swift
|
||||
try? AudioSessionController.set(category: .playback)
|
||||
try? AudioSessionController.shared.set(category: .playback)
|
||||
//...
|
||||
// You should wait with activating the session until you actually start playback of audio.
|
||||
// This is to avoid interrupting other audio without the need to do it.
|
||||
try? AudioSessionController.activateSession()
|
||||
try? AudioSessionController.shared.activateSession()
|
||||
```
|
||||
|
||||
If you want audio to continue playing when the app is inactive, remember to activate background audio:
|
||||
**Important**: If you want audio to continue playing when the app is inactive, remember to activate background audio:
|
||||
App Settings -> Capabilities -> Background Modes -> Check 'Audio, AirPlay, and Picture in Picture'.
|
||||
|
||||
#### Interruptions
|
||||
If you are using the AudioSessionController for setting up the audio session, you can use it to handle interruptions too.
|
||||
If you are using the `AudioSessionController` for setting up the audio session, you can use it to handle interruptions too.
|
||||
Implement `AudioSessionControllerDelegate` and you will be notified by `handleInterruption(type: AVAudioSessionInterruptionType)`.
|
||||
If you are storing progress for playback time on items when the app quits, it can be a good idea to do it on interruptions as well.
|
||||
To disable interruption notifcations set `isObservingForInterruptions` to `false`.
|
||||
|
||||
### Now Playing Info
|
||||
The `AudioPlayer` will automatically update the `MPNowPlayingInfoCenter` with artist, title, album, artwork, time if the passed in `AudioItem` supports this.
|
||||
If you need to set additional properties for some items use `AudioPlayer.add(property:)`. Available properties can be found in `NowPlayingInfoProperty`.
|
||||
The `AudioPlayer` can automatically update `nowPlayingInfo` for you. This requires `automaticallyUpdateNowPlayingInfo` to be true (default), and that the `AudioItem` that is passed in return values for the getters. The `AudioPlayer` will update: artist, title, album, artwork, elapsed time, duration and rate.
|
||||
|
||||
Additional properties for items can be set by accessing the setter of the `nowPlayingInforController`:
|
||||
```swift
|
||||
let player = AudioPlayer()
|
||||
player.load(item: someItem)
|
||||
player.nowPlayingInfoController.set(keyValue: NowPlayingInfoProperty.isLiveStream(true))
|
||||
```
|
||||
The set(keyValue:) and set(keyValues:) accept both `MediaItemProperty` and `NowPlayingInfoProperty`.
|
||||
|
||||
The info can be forced to reload/update from the `AudioPlayer`.
|
||||
```swift
|
||||
audioPlayer.loadNowPlayingMetaValues()
|
||||
audioPlayer.updateNowPlayingPlaybackValues()
|
||||
```
|
||||
The current info can be cleared with:
|
||||
```swift
|
||||
audioPlayer.nowPlayingInfoController.clear()
|
||||
```
|
||||
|
||||
### Remote Commands
|
||||
**First** go to App Settings -> Capabilites -> Background Modes -> Check 'Remote notifications'
|
||||
|
||||
The player will handle remote commands received from `MPRemoteCommandCenter`'s shared instance, enabled by:
|
||||
To enable remote commands for the player you need to populate the RemoteCommands array for the player:
|
||||
```swift
|
||||
audioPlayer.remoteCommands = [
|
||||
.play,
|
||||
@@ -98,20 +147,20 @@ audioPlayer.remoteCommands = [
|
||||
.skipBackward(intervals: [30]),
|
||||
]
|
||||
```
|
||||
These commands will be activated for each `AudioItem`. If you need some audio items to have different commands, implement `RemoteCommandable` in a custom `AudioItem`-subclass. These commands will override the commands found in `AudioPlayer.remoteCommands` so make sure to supply all commands you need for that particular `AudioItem`.
|
||||
|
||||
These commands will be activated for each `AudioItem`. If you need some audio items to have different commands, implement `RemoteCommandable`. These commands will override the commands found in `AudioPlayer.remoteCommands` so make sure to supply all commands you need for that particular `AudioItem`.
|
||||
#### Custom handlers for remote commands
|
||||
To supply custom handlers for your remote commands, just override the handlers contained in the player's `RemoteCommandController`:
|
||||
```swift
|
||||
let player = QueuedAudioPlayer()
|
||||
player.remoteCommandController.handlePlayCommand = { (event) in
|
||||
// Handle remote command here.
|
||||
}
|
||||
```
|
||||
All available overrides can be found by looking at `RemoteCommandController`.
|
||||
|
||||
**Remember** to go to App Settings -> Capabilites -> Background Modes -> Check 'Remote notifications'
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Currently some configuration options are supported:
|
||||
+ `automaticallyWaitsToMinimizeStalling`: Whether the player should delay playback start to minimize stalling. If you are streaming large audio files and playback start is slow, it can help to set this to `false`. Default is `true`.
|
||||
+ `bufferDuration`: The amount of seconds to be buffered by the player. Does not have any effect if `automaticallyWaitsToMinimizeStalling` is set to `true`.
|
||||
+ `timeEventFrequency`: This decides how ofen the delegate should be notified that a time unit elapsed in the playback.
|
||||
+ `volume`: The volume of the player. From 0.0 to 1.0.
|
||||
+ `automaticallyUpdateNowPlayingInfo`: If you want to handle updating of the `MPNowPlayingInfoCenter` yourself, set this to `false`. Default is `true`.
|
||||
### Start playback from a certain point in time
|
||||
Make your `AudioItem`-subclass conform to `InitialTiming` to be able to start playback from a certain time.
|
||||
|
||||
## Author
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudio'
|
||||
s.version = '0.3.4'
|
||||
s.version = '0.7.0'
|
||||
s.summary = 'Easy audio streaming for iOS'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
|
||||
Regular → Executable
+114
-207
@@ -10,19 +10,15 @@ import Foundation
|
||||
import AVFoundation
|
||||
import MediaPlayer
|
||||
|
||||
|
||||
protocol AVPlayerWrapperDelegate: class {
|
||||
|
||||
func AVWrapper(didChangeState state: AVPlayerWrapperState)
|
||||
func AVWrapperItemDidComplete()
|
||||
func AVWrapper(secondsElapsed seconds: Double)
|
||||
func AVWrapper(failedWithError error: Error?)
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool)
|
||||
func AVWrapper(didUpdateDuration duration: Double)
|
||||
|
||||
public enum PlaybackEndedReason: String {
|
||||
case playedUntilEnd
|
||||
case playerStopped
|
||||
case skippedToNext
|
||||
case skippedToPrevious
|
||||
case jumpedToIndex
|
||||
}
|
||||
|
||||
class AVPlayerWrapper {
|
||||
class AVPlayerWrapper: AVPlayerWrapperProtocol {
|
||||
|
||||
struct Constants {
|
||||
static let assetPlayableKey = "playable"
|
||||
@@ -35,19 +31,12 @@ class AVPlayerWrapper {
|
||||
let playerTimeObserver: AVPlayerTimeObserver
|
||||
let playerItemNotificationObserver: AVPlayerItemNotificationObserver
|
||||
let playerItemObserver: AVPlayerItemObserver
|
||||
|
||||
|
||||
/**
|
||||
True if the last call to load(from:playWhenReady) had playWhenReady=true.
|
||||
Cannot be set directly.
|
||||
*/
|
||||
var playWhenReady: Bool { return _playWhenReady }
|
||||
|
||||
private var _playWhenReady: Bool = true
|
||||
|
||||
/**
|
||||
The current `AudioPlayerState` of the player.
|
||||
*/
|
||||
var state: AVPlayerWrapperState { return _state }
|
||||
fileprivate var _playWhenReady: Bool = true
|
||||
fileprivate var _initialTime: TimeInterval?
|
||||
|
||||
fileprivate var _state: AVPlayerWrapperState = AVPlayerWrapperState.idle {
|
||||
didSet {
|
||||
@@ -57,102 +46,12 @@ class AVPlayerWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The delegate receiving events.
|
||||
*/
|
||||
weak var delegate: AVPlayerWrapperDelegate?
|
||||
|
||||
// MARK: - AVPlayer Get Properties
|
||||
|
||||
/**
|
||||
The AVAsset for the currentItem.
|
||||
*/
|
||||
var currentAsset: AVAsset? {
|
||||
return currentItem?.asset
|
||||
}
|
||||
|
||||
/**
|
||||
The current item of the AVPlayer.
|
||||
*/
|
||||
var currentItem: AVPlayerItem? {
|
||||
return avPlayer.currentItem
|
||||
}
|
||||
|
||||
/**
|
||||
The duration of the current item.
|
||||
*/
|
||||
var duration: Double {
|
||||
if let seconds = currentItem?.duration.seconds, !seconds.isNaN {
|
||||
return seconds
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
The current time of the item in the player.
|
||||
*/
|
||||
var currentTime: Double {
|
||||
let seconds = avPlayer.currentTime().seconds
|
||||
return seconds.isNaN ? 0 : seconds
|
||||
}
|
||||
|
||||
/**
|
||||
The rate of the AVPlayer
|
||||
*/
|
||||
var rate: Float {
|
||||
return avPlayer.rate
|
||||
}
|
||||
|
||||
// MARK: - AVPlayer Config Properties
|
||||
|
||||
/**
|
||||
Indicates wether the player should automatically delay playback in order to minimize stalling.
|
||||
[Read more from Apple Documentation](https://developer.apple.com/documentation/avfoundation/avplayer/1643482-automaticallywaitstominimizestal)
|
||||
*/
|
||||
var automaticallyWaitsToMinimizeStalling: Bool {
|
||||
get { return avPlayer.automaticallyWaitsToMinimizeStalling }
|
||||
set { avPlayer.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
The amount of seconds to be buffered by the player. Default value is 0 seconds, this means the AVPlayer will choose an appropriate level of buffering.
|
||||
|
||||
[Read more from Apple Documentation](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration)
|
||||
|
||||
- Important: This setting will have no effect if `automaticallyWaitsToMinimizeStalling` is set to `true`
|
||||
*/
|
||||
var bufferDuration: TimeInterval
|
||||
|
||||
/**
|
||||
Set this to decide how often the player should call the delegate with time progress events.
|
||||
*/
|
||||
var timeEventFrequency: TimeEventFrequency {
|
||||
didSet {
|
||||
playerTimeObserver.periodicObserverTimeInterval = timeEventFrequency.getTime()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The player volume, from 0.0 to 1.0
|
||||
Default is 1.0
|
||||
*/
|
||||
public var volume: Float {
|
||||
get { return avPlayer.volume }
|
||||
set { avPlayer.volume = newValue }
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
|
||||
public init(timeEventFrequency: TimeEventFrequency = .everySecond) {
|
||||
|
||||
self.avPlayer = AVPlayer()
|
||||
public init(avPlayer: AVPlayer = AVPlayer()) {
|
||||
self.avPlayer = avPlayer
|
||||
self.playerObserver = AVPlayerObserver(player: avPlayer)
|
||||
self.playerTimeObserver = AVPlayerTimeObserver(player: avPlayer, periodicObserverTimeInterval: timeEventFrequency.getTime())
|
||||
self.playerItemNotificationObserver = AVPlayerItemNotificationObserver()
|
||||
self.playerItemObserver = AVPlayerItemObserver()
|
||||
|
||||
self.bufferDuration = 0
|
||||
self.timeEventFrequency = timeEventFrequency
|
||||
|
||||
self.playerObserver.delegate = self
|
||||
self.playerTimeObserver.delegate = self
|
||||
@@ -162,117 +61,113 @@ class AVPlayerWrapper {
|
||||
playerTimeObserver.registerForPeriodicTimeEvents()
|
||||
}
|
||||
|
||||
/**
|
||||
Start playback.
|
||||
|
||||
- throws: APError.PlaybackError
|
||||
*/
|
||||
func play() throws {
|
||||
guard currentItem != nil else {
|
||||
throw APError.PlaybackError.noLoadedItem
|
||||
// MARK: - AVPlayerWrapperProtocol
|
||||
|
||||
var state: AVPlayerWrapperState {
|
||||
return _state
|
||||
}
|
||||
|
||||
var reasonForWaitingToPlay: AVPlayer.WaitingReason? {
|
||||
return avPlayer.reasonForWaitingToPlay
|
||||
}
|
||||
|
||||
var currentItem: AVPlayerItem? {
|
||||
return avPlayer.currentItem
|
||||
}
|
||||
|
||||
var automaticallyWaitsToMinimizeStalling: Bool {
|
||||
get { return avPlayer.automaticallyWaitsToMinimizeStalling }
|
||||
set { avPlayer.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
}
|
||||
|
||||
var currentTime: TimeInterval {
|
||||
let seconds = avPlayer.currentTime().seconds
|
||||
return seconds.isNaN ? 0 : seconds
|
||||
}
|
||||
|
||||
var duration: TimeInterval {
|
||||
if let seconds = currentItem?.asset.duration.seconds, !seconds.isNaN {
|
||||
return seconds
|
||||
}
|
||||
|
||||
guard avPlayer.timeControlStatus == .paused else {
|
||||
return
|
||||
else if let seconds = currentItem?.duration.seconds, !seconds.isNaN {
|
||||
return seconds
|
||||
}
|
||||
|
||||
else if let seconds = currentItem?.loadedTimeRanges.first?.timeRangeValue.duration.seconds,
|
||||
!seconds.isNaN {
|
||||
return seconds
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
weak var delegate: AVPlayerWrapperDelegate? = nil
|
||||
|
||||
var bufferDuration: TimeInterval = 0
|
||||
|
||||
var timeEventFrequency: TimeEventFrequency = .everySecond {
|
||||
didSet {
|
||||
playerTimeObserver.periodicObserverTimeInterval = timeEventFrequency.getTime()
|
||||
}
|
||||
}
|
||||
|
||||
var rate: Float {
|
||||
get { return avPlayer.rate }
|
||||
set { avPlayer.rate = newValue }
|
||||
}
|
||||
|
||||
var volume: Float {
|
||||
get { return avPlayer.volume }
|
||||
set { avPlayer.volume = newValue }
|
||||
}
|
||||
|
||||
var isMuted: Bool {
|
||||
get { return avPlayer.isMuted }
|
||||
set { avPlayer.isMuted = newValue }
|
||||
}
|
||||
|
||||
func play() {
|
||||
avPlayer.play()
|
||||
}
|
||||
|
||||
/**
|
||||
Will pause playback.
|
||||
|
||||
- throws: APError.PlaybackError
|
||||
*/
|
||||
func pause() throws {
|
||||
guard currentItem != nil else {
|
||||
throw APError.PlaybackError.noLoadedItem
|
||||
}
|
||||
|
||||
guard avPlayer.timeControlStatus == .playing || avPlayer.timeControlStatus == .waitingToPlayAtSpecifiedRate else {
|
||||
return
|
||||
}
|
||||
|
||||
func pause() {
|
||||
avPlayer.pause()
|
||||
}
|
||||
|
||||
/**
|
||||
Will toggle playback.
|
||||
*/
|
||||
func togglePlaying() throws {
|
||||
func togglePlaying() {
|
||||
switch avPlayer.timeControlStatus {
|
||||
case .playing, .waitingToPlayAtSpecifiedRate:
|
||||
try pause()
|
||||
pause()
|
||||
case .paused:
|
||||
try play()
|
||||
play()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Stop the player and remove the currently playing item.
|
||||
*/
|
||||
func stop() {
|
||||
try? pause()
|
||||
pause()
|
||||
reset(soft: false)
|
||||
}
|
||||
|
||||
/**
|
||||
Seek to a point in the item.
|
||||
|
||||
- parameter seconds: The point to move the player head, in seconds. If the given value is less than 0, 0 is used. If the value is larger than the duration, the duration is used.
|
||||
- throws: `APError.PlaybackError`
|
||||
*/
|
||||
func seek(to seconds: TimeInterval) throws {
|
||||
guard currentItem != nil else {
|
||||
throw APError.PlaybackError.noLoadedItem
|
||||
}
|
||||
let millis = Int64(max(min(seconds, duration), 0) * 1000)
|
||||
let time = CMTime(value: millis, timescale: 1000)
|
||||
avPlayer.seek(to: time) { (finished) in
|
||||
func seek(to seconds: TimeInterval) {
|
||||
avPlayer.seek(to: CMTimeMakeWithSeconds(seconds, preferredTimescale: 1000)) { (finished) in
|
||||
if let _ = self._initialTime {
|
||||
self._initialTime = nil
|
||||
if self._playWhenReady {
|
||||
self.play()
|
||||
}
|
||||
}
|
||||
self.delegate?.AVWrapper(seekTo: Int(seconds), didFinish: finished)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Load an item from a URL string. Use this when streaming sound.
|
||||
|
||||
- parameter urlString: The AudioSource to load the item from.
|
||||
- parameter playWhenReady: Whether playback should start immediately when the item is ready. Default is `true`
|
||||
*/
|
||||
func load(fromUrlString urlString: String, playWhenReady: Bool = true) throws {
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
throw APError.LoadError.invalidSourceUrl(urlString)
|
||||
}
|
||||
|
||||
self.load(from: url, playWhenReady: playWhenReady)
|
||||
}
|
||||
|
||||
/**
|
||||
Load an item from a file. Use this when playing local.
|
||||
|
||||
- parameter filePath: The path to the sound file.
|
||||
- parameter playWhenReady: Whether playback should start immediately when the item is ready. Default is `true`
|
||||
*/
|
||||
func load(fromFilePath filePath: String, playWhenReady: Bool = true) throws {
|
||||
let url = URL(fileURLWithPath: filePath)
|
||||
self.load(from: url, playWhenReady: playWhenReady)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func load(from url: URL, playWhenReady: Bool) {
|
||||
|
||||
|
||||
func load(from url: URL, playWhenReady: Bool) {
|
||||
reset(soft: true)
|
||||
_playWhenReady = playWhenReady
|
||||
_state = .loading
|
||||
|
||||
|
||||
// Set item
|
||||
let currentAsset = AVURLAsset(url: url)
|
||||
let currentItem = AVPlayerItem(asset: currentAsset, automaticallyLoadedAssetKeys: [Constants.assetPlayableKey])
|
||||
currentItem.preferredForwardBufferDuration = bufferDuration
|
||||
avPlayer.replaceCurrentItem(with: currentItem)
|
||||
|
||||
|
||||
// Register for events
|
||||
playerTimeObserver.registerForBoundaryTimeEvents()
|
||||
playerObserver.startObserving()
|
||||
@@ -280,15 +175,22 @@ class AVPlayerWrapper {
|
||||
playerItemObserver.startObserving(item: currentItem)
|
||||
}
|
||||
|
||||
/**
|
||||
Reset to get ready for playing from a different source.
|
||||
*/
|
||||
func load(from url: URL, playWhenReady: Bool, initialTime: TimeInterval?) {
|
||||
_initialTime = initialTime
|
||||
self.pause()
|
||||
self.load(from: url, playWhenReady: playWhenReady)
|
||||
}
|
||||
|
||||
// MARK: - Util
|
||||
|
||||
private func reset(soft: Bool) {
|
||||
playerItemObserver.stopObservingCurrentItem()
|
||||
playerTimeObserver.unregisterForBoundaryTimeEvents()
|
||||
playerItemNotificationObserver.stopObservingCurrentItem()
|
||||
|
||||
if !soft {
|
||||
avPlayer.replaceCurrentItem(with: nil)
|
||||
}
|
||||
playerTimeObserver.unregisterForBoundaryTimeEvents()
|
||||
playerItemNotificationObserver.stopObservingCurrentItem()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -297,7 +199,7 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
|
||||
|
||||
// MARK: - AVPlayerObserverDelegate
|
||||
|
||||
func player(didChangeTimeControlStatus status: AVPlayerTimeControlStatus) {
|
||||
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus) {
|
||||
switch status {
|
||||
case .paused:
|
||||
if currentItem == nil {
|
||||
@@ -313,14 +215,19 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func player(statusDidChange status: AVPlayerStatus) {
|
||||
func player(statusDidChange status: AVPlayer.Status) {
|
||||
switch status {
|
||||
|
||||
|
||||
case .readyToPlay:
|
||||
self._state = .ready
|
||||
if _playWhenReady {
|
||||
try? self.play()
|
||||
|
||||
if let initialTime = _initialTime {
|
||||
self.seek(to: initialTime)
|
||||
}
|
||||
else if _playWhenReady {
|
||||
self.play()
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case .failed:
|
||||
@@ -353,7 +260,7 @@ extension AVPlayerWrapper: AVPlayerItemNotificationObserverDelegate {
|
||||
// MARK: - AVPlayerItemNotificationObserverDelegate
|
||||
|
||||
func itemDidPlayToEndTime() {
|
||||
delegate?.AVWrapperItemDidComplete()
|
||||
delegate?.AVWrapperItemDidPlayToEndTime()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// AVPlayerWrapperDelegate.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 26/10/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
protocol AVPlayerWrapperDelegate: class {
|
||||
|
||||
func AVWrapper(didChangeState state: AVPlayerWrapperState)
|
||||
func AVWrapper(secondsElapsed seconds: Double)
|
||||
func AVWrapper(failedWithError error: Error?)
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool)
|
||||
func AVWrapper(didUpdateDuration duration: Double)
|
||||
func AVWrapperItemDidPlayToEndTime()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// AVPlayerWrapperProtocol.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 26/10/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
|
||||
protocol AVPlayerWrapperProtocol {
|
||||
|
||||
var state: AVPlayerWrapperState { get }
|
||||
|
||||
var currentItem: AVPlayerItem? { get }
|
||||
|
||||
var currentTime: TimeInterval { get }
|
||||
|
||||
var duration: TimeInterval { get }
|
||||
|
||||
var reasonForWaitingToPlay: AVPlayer.WaitingReason? { get }
|
||||
|
||||
|
||||
var rate: Float { get set }
|
||||
|
||||
var delegate: AVPlayerWrapperDelegate? { get set }
|
||||
|
||||
var bufferDuration: TimeInterval { get set }
|
||||
|
||||
var timeEventFrequency: TimeEventFrequency { get set }
|
||||
|
||||
var volume: Float { get set }
|
||||
|
||||
var isMuted: Bool { get set }
|
||||
|
||||
var automaticallyWaitsToMinimizeStalling: Bool { get set }
|
||||
|
||||
|
||||
func play()
|
||||
|
||||
func pause()
|
||||
|
||||
func togglePlaying()
|
||||
|
||||
func stop()
|
||||
|
||||
func seek(to seconds: TimeInterval)
|
||||
|
||||
func load(from url: URL, playWhenReady: Bool)
|
||||
|
||||
func load(from url: URL, playWhenReady: Bool, initialTime: TimeInterval?)
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public enum AVPlayerWrapperState: String {
|
||||
/// The player is playing.
|
||||
case playing
|
||||
|
||||
/// No item loaded, the player is stopped. Call play(from:) to start loading.
|
||||
/// No item loaded, the player is stopped.
|
||||
case idle
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+54
-2
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
public enum SourceType {
|
||||
case stream
|
||||
@@ -23,9 +24,20 @@ public protocol AudioItem {
|
||||
|
||||
}
|
||||
|
||||
public struct DefaultAudioItem: AudioItem {
|
||||
/// Make your `AudioItem`-subclass conform to this protocol to control which AVAudioTimePitchAlgorithm is used for each item.
|
||||
public protocol TimePitching {
|
||||
|
||||
func getPitchAlgorithmType() -> AVAudioTimePitchAlgorithm
|
||||
|
||||
}
|
||||
|
||||
/// Make your `AudioItem`-subclass conform to this protocol to control enable the ability to start an item at a specific time of playback.
|
||||
public protocol InitialTiming {
|
||||
func getInitialTime() -> TimeInterval
|
||||
}
|
||||
|
||||
public class DefaultAudioItem: AudioItem {
|
||||
|
||||
public var audioUrl: String
|
||||
|
||||
public var artist: String?
|
||||
@@ -66,10 +78,50 @@ public struct DefaultAudioItem: AudioItem {
|
||||
public func getSourceType() -> SourceType {
|
||||
return sourceType
|
||||
}
|
||||
|
||||
|
||||
public func getArtwork(_ handler: @escaping (UIImage?) -> Void) {
|
||||
handler(artwork)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// An AudioItem that also conforms to the `TimePitching`-protocol
|
||||
public class DefaultAudioItemTimePitching: DefaultAudioItem, TimePitching {
|
||||
|
||||
public var pitchAlgorithmType: AVAudioTimePitchAlgorithm
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
self.pitchAlgorithmType = AVAudioTimePitchAlgorithm.lowQualityZeroLatency
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?, audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm) {
|
||||
self.pitchAlgorithmType = audioTimePitchAlgorithm
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public func getPitchAlgorithmType() -> AVAudioTimePitchAlgorithm {
|
||||
return pitchAlgorithmType
|
||||
}
|
||||
}
|
||||
|
||||
/// An AudioItem that also conforms to the `InitialTiming`-protocol
|
||||
public class DefaultAudioItemInitialTime: DefaultAudioItem, InitialTiming {
|
||||
|
||||
public var initialTime: TimeInterval
|
||||
|
||||
public override init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?) {
|
||||
self.initialTime = 0.0
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public init(audioUrl: String, artist: String?, title: String?, albumTitle: String?, sourceType: SourceType, artwork: UIImage?, initialTime: TimeInterval) {
|
||||
self.initialTime = initialTime
|
||||
super.init(audioUrl: audioUrl, artist: artist, title: title, albumTitle: albumTitle, sourceType: sourceType, artwork: artwork)
|
||||
}
|
||||
|
||||
public func getInitialTime() -> TimeInterval {
|
||||
return initialTime
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+149
-90
@@ -10,11 +10,12 @@ import MediaPlayer
|
||||
|
||||
public typealias AudioPlayerState = AVPlayerWrapperState
|
||||
|
||||
@available(*, deprecated, message: "Delegates will be removed in future versions of SwiftAudio. Use event handlers instead.")
|
||||
public protocol AudioPlayerDelegate: class {
|
||||
|
||||
func audioPlayer(playerDidChangeState state: AudioPlayerState)
|
||||
|
||||
func audioPlayerItemDidComplete()
|
||||
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason)
|
||||
|
||||
func audioPlayer(secondsElapsed seconds: Double)
|
||||
|
||||
@@ -23,22 +24,25 @@ public protocol AudioPlayerDelegate: class {
|
||||
func audioPlayer(seekTo seconds: Int, didFinish: Bool)
|
||||
|
||||
func audioPlayer(didUpdateDuration duration: Double)
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
The main AudioPlayer.
|
||||
- warning: DO NOT USE THIS CLASS, use `SimpleAudioPlayer` or `QueuedAudioPlayer`
|
||||
*/
|
||||
public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
let wrapper: AVPlayerWrapper
|
||||
let nowPlayingInfoController: NowPlayingInfoController
|
||||
let remoteCommandController: RemoteCommandController
|
||||
private var _wrapper: AVPlayerWrapperProtocol
|
||||
|
||||
/// The wrapper around the underlying AVPlayer
|
||||
var wrapper: AVPlayerWrapperProtocol {
|
||||
return _wrapper
|
||||
}
|
||||
|
||||
public let nowPlayingInfoController: NowPlayingInfoControllerProtocol
|
||||
public let remoteCommandController: RemoteCommandController
|
||||
|
||||
public let event = EventHolder()
|
||||
public weak var delegate: AudioPlayerDelegate?
|
||||
|
||||
var _currentItem: AudioItem?
|
||||
|
||||
public weak var delegate: AudioPlayerDelegate?
|
||||
public var currentItem: AudioItem? {
|
||||
return _currentItem
|
||||
}
|
||||
@@ -48,11 +52,18 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
*/
|
||||
public var automaticallyUpdateNowPlayingInfo: Bool = true
|
||||
|
||||
/**
|
||||
Controls the time pitch algorithm applied to each item loaded into the player.
|
||||
If the loaded `AudioItem` conforms to `TimePitcher`-protocol this will be overriden.
|
||||
*/
|
||||
public var audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.lowQualityZeroLatency
|
||||
|
||||
/**
|
||||
Default remote commands to use for each playing item
|
||||
*/
|
||||
public var remoteCommands: [RemoteCommand] = []
|
||||
|
||||
|
||||
// MARK: - Getters from AVPlayerWrapper
|
||||
|
||||
/**
|
||||
@@ -69,13 +80,6 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
return wrapper.duration
|
||||
}
|
||||
|
||||
/**
|
||||
The current rate of the underlying `AudioPlayer`.
|
||||
*/
|
||||
public var rate: Float {
|
||||
return wrapper.rate
|
||||
}
|
||||
|
||||
/**
|
||||
The current state of the underlying `AudioPlayer`.
|
||||
*/
|
||||
@@ -85,42 +89,47 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
// MARK: - Setters for AVPlayerWrapper
|
||||
|
||||
/**
|
||||
Indicates wether the player should automatically delay playback in order to minimize stalling.
|
||||
[Read more from Apple Documentation](https://developer.apple.com/documentation/avfoundation/avplayer/1643482-automaticallywaitstominimizestal)
|
||||
*/
|
||||
public var automaticallyWaitsToMinimizeStalling: Bool {
|
||||
get { return wrapper.automaticallyWaitsToMinimizeStalling }
|
||||
set { wrapper.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
The amount of seconds to be buffered by the player. Default value is 0 seconds, this means the AVPlayer will choose an appropriate level of buffering.
|
||||
|
||||
[Read more from Apple Documentation](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration)
|
||||
|
||||
- Important: This setting will have no effect if `automaticallyWaitsToMinimizeStalling` is set to `true`
|
||||
- Important: This setting will have no effect if `automaticallyWaitsToMinimizeStalling` is set to `true` in the AVPlayer
|
||||
*/
|
||||
public var bufferDuration: TimeInterval {
|
||||
get { return wrapper.bufferDuration }
|
||||
set { wrapper.bufferDuration = newValue }
|
||||
set { _wrapper.bufferDuration = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
Set this to decide how often the player should call the delegate with time progress events.
|
||||
*/
|
||||
public var timeEventFrquency: TimeEventFrequency {
|
||||
public var timeEventFrequency: TimeEventFrequency {
|
||||
get { return wrapper.timeEventFrequency }
|
||||
set { wrapper.timeEventFrequency = newValue }
|
||||
set { _wrapper.timeEventFrequency = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
The player volume, from 0.0 to 1.0
|
||||
Default is 1.0
|
||||
Indicates whether the player should automatically delay playback in order to minimize stalling
|
||||
*/
|
||||
public var automaticallyWaitsToMinimizeStalling: Bool {
|
||||
get { return wrapper.automaticallyWaitsToMinimizeStalling }
|
||||
set { _wrapper.automaticallyWaitsToMinimizeStalling = newValue }
|
||||
}
|
||||
|
||||
public var volume: Float {
|
||||
get { return wrapper.volume }
|
||||
set { wrapper.volume = newValue }
|
||||
set { _wrapper.volume = newValue }
|
||||
}
|
||||
|
||||
public var isMuted: Bool {
|
||||
get { return wrapper.isMuted }
|
||||
set { _wrapper.isMuted = newValue }
|
||||
}
|
||||
|
||||
public var rate: Float {
|
||||
get { return wrapper.rate }
|
||||
set { _wrapper.rate = newValue }
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
@@ -130,12 +139,14 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
- parameter infoCenter: The InfoCenter to update. Default is `MPNowPlayingInfoCenter.default()`.
|
||||
*/
|
||||
init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
self.wrapper = AVPlayerWrapper()
|
||||
self.nowPlayingInfoController = NowPlayingInfoController(infoCenter: infoCenter)
|
||||
self.remoteCommandController = RemoteCommandController()
|
||||
public init(avPlayer: AVPlayer = AVPlayer(),
|
||||
nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(),
|
||||
remoteCommandController: RemoteCommandController = RemoteCommandController()) {
|
||||
self._wrapper = AVPlayerWrapper(avPlayer: avPlayer)
|
||||
self.nowPlayingInfoController = nowPlayingInfoController
|
||||
self.remoteCommandController = remoteCommandController
|
||||
|
||||
self.wrapper.delegate = self
|
||||
self._wrapper.delegate = self
|
||||
self.remoteCommandController.audioPlayer = self
|
||||
}
|
||||
|
||||
@@ -147,40 +158,58 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
- parameter item: The AudioItem to load. The info given in this item is the one used for the InfoCenter.
|
||||
- parameter playWhenReady: Immediately start playback when the item is ready. Default is `true`. If you disable this you have to call play() or togglePlay() when the `state` switches to `ready`.
|
||||
*/
|
||||
func loadItem(_ item: AudioItem, playWhenReady: Bool = true) throws {
|
||||
print("Loading: \(item)")
|
||||
public func load(item: AudioItem, playWhenReady: Bool = true) throws {
|
||||
let url: URL
|
||||
switch item.getSourceType() {
|
||||
case .stream:
|
||||
try self.wrapper.load(fromUrlString: item.getSourceUrl(), playWhenReady: playWhenReady)
|
||||
if let itemUrl = URL(string: item.getSourceUrl()) {
|
||||
url = itemUrl
|
||||
}
|
||||
else {
|
||||
throw APError.LoadError.invalidSourceUrl(item.getSourceUrl())
|
||||
}
|
||||
case .file:
|
||||
try self.wrapper.load(fromFilePath: item.getSourceUrl(), playWhenReady: playWhenReady)
|
||||
url = URL(fileURLWithPath: item.getSourceUrl())
|
||||
}
|
||||
|
||||
wrapper.load(from: url,
|
||||
playWhenReady: playWhenReady,
|
||||
initialTime: (item as? InitialTiming)?.getInitialTime())
|
||||
|
||||
if let item = item as? TimePitching {
|
||||
wrapper.currentItem?.audioTimePitchAlgorithm = item.getPitchAlgorithmType()
|
||||
}
|
||||
else {
|
||||
wrapper.currentItem?.audioTimePitchAlgorithm = audioTimePitchAlgorithm
|
||||
}
|
||||
|
||||
self._currentItem = item
|
||||
set(item: item)
|
||||
setArtwork(forItem: item)
|
||||
|
||||
if (automaticallyUpdateNowPlayingInfo) {
|
||||
self.loadNowPlayingMetaValues()
|
||||
}
|
||||
enableRemoteCommands(forItem: item)
|
||||
}
|
||||
|
||||
/**
|
||||
Toggle playback status.
|
||||
*/
|
||||
public func togglePlaying() throws {
|
||||
try self.wrapper.togglePlaying()
|
||||
public func togglePlaying() {
|
||||
self.wrapper.togglePlaying()
|
||||
}
|
||||
|
||||
/**
|
||||
Start playback
|
||||
*/
|
||||
public func play() throws {
|
||||
try self.wrapper.play()
|
||||
public func play() {
|
||||
self.wrapper.play()
|
||||
}
|
||||
|
||||
/**
|
||||
Pause playback
|
||||
*/
|
||||
public func pause() throws {
|
||||
try self.wrapper.pause()
|
||||
public func pause() {
|
||||
self.wrapper.pause()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,21 +218,22 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
public func stop() {
|
||||
self.reset()
|
||||
self.wrapper.stop()
|
||||
self.event.playbackEnd.emit(data: .playerStopped)
|
||||
self.delegate?.audioPlayer(itemPlaybackEndedWithReason: .playerStopped)
|
||||
}
|
||||
|
||||
/**
|
||||
Seek to a specific time in the item.
|
||||
*/
|
||||
public func seek(to seconds: TimeInterval) throws {
|
||||
try self.wrapper.seek(to: seconds)
|
||||
public func seek(to seconds: TimeInterval) {
|
||||
if automaticallyUpdateNowPlayingInfo {
|
||||
self.updateNowPlayingCurrentTime(seconds)
|
||||
}
|
||||
self.wrapper.seek(to: seconds)
|
||||
}
|
||||
|
||||
// MARK: - Remote Command Center
|
||||
|
||||
/**
|
||||
Set the remote commands that should be activated and handled.
|
||||
Calling this will disable all earlier enabled commands, so include all commands you need.
|
||||
*/
|
||||
func enableRemoteCommands(_ commands: [RemoteCommand]) {
|
||||
self.remoteCommandController.enable(commands: commands)
|
||||
}
|
||||
@@ -220,53 +250,66 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
// MARK: - NowPlayingInfo
|
||||
|
||||
/**
|
||||
Reloads the NowPlayingInfo from the current AudioItem.
|
||||
Loads NowPlayingInfo-meta values with the values found in the current `AudioItem`. Use this if a change to the `AudioItem` is made and you want to update the `NowPlayingInfoController`s values.
|
||||
|
||||
Reloads:
|
||||
- Artist
|
||||
- Title
|
||||
- Album title
|
||||
- Album artwork
|
||||
*/
|
||||
public func reloadNowPlayingInfo() {
|
||||
public func loadNowPlayingMetaValues() {
|
||||
guard let item = currentItem else { return }
|
||||
set(item: item)
|
||||
setArtwork(forItem: item)
|
||||
updatePlaybackValues()
|
||||
}
|
||||
|
||||
public func add(property: NowPlayingInfoKeyValue) {
|
||||
self.nowPlayingInfoController.set(keyValue: property)
|
||||
}
|
||||
|
||||
func set(item: AudioItem) {
|
||||
guard automaticallyUpdateNowPlayingInfo else { return }
|
||||
|
||||
|
||||
nowPlayingInfoController.set(keyValues: [
|
||||
MediaItemProperty.artist(item.getArtist()),
|
||||
MediaItemProperty.title(item.getTitle()),
|
||||
MediaItemProperty.albumTitle(item.getAlbumTitle()),
|
||||
])
|
||||
])
|
||||
|
||||
loadArtwork(forItem: item)
|
||||
}
|
||||
|
||||
func setArtwork(forItem item: AudioItem) {
|
||||
guard automaticallyUpdateNowPlayingInfo else { return }
|
||||
/**
|
||||
Resyncs the playbackvalues of the currently playing `AudioItem`.
|
||||
|
||||
Will resync:
|
||||
- Current time
|
||||
- Duration
|
||||
- Playback rate
|
||||
*/
|
||||
public func updateNowPlayingPlaybackValues() {
|
||||
updateNowPlayingDuration(duration)
|
||||
updateNowPlayingCurrentTime(currentTime)
|
||||
updateNowPlayingRate(rate)
|
||||
}
|
||||
|
||||
private func updateNowPlayingDuration(_ duration: Double) {
|
||||
nowPlayingInfoController.set(keyValue: MediaItemProperty.duration(duration))
|
||||
}
|
||||
|
||||
private func updateNowPlayingRate(_ rate: Float) {
|
||||
nowPlayingInfoController.set(keyValue: NowPlayingInfoProperty.playbackRate(Double(rate)))
|
||||
}
|
||||
|
||||
private func updateNowPlayingCurrentTime(_ currentTime: Double) {
|
||||
nowPlayingInfoController.set(keyValue: NowPlayingInfoProperty.elapsedPlaybackTime(currentTime))
|
||||
}
|
||||
|
||||
private func loadArtwork(forItem item: AudioItem) {
|
||||
item.getArtwork { (image) in
|
||||
if let image = image {
|
||||
|
||||
let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { (size) -> UIImage in
|
||||
return image
|
||||
})
|
||||
|
||||
self.nowPlayingInfoController.set(keyValue: MediaItemProperty.artwork(artwork))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updatePlaybackValues() {
|
||||
guard automaticallyUpdateNowPlayingInfo else { return }
|
||||
nowPlayingInfoController.set(keyValue: NowPlayingInfoProperty.elapsedPlaybackTime(wrapper.currentTime))
|
||||
nowPlayingInfoController.set(keyValue: MediaItemProperty.duration(wrapper.duration))
|
||||
nowPlayingInfoController.set(keyValue: NowPlayingInfoProperty.playbackRate(Double(wrapper.rate)))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func reset() {
|
||||
func reset() {
|
||||
self._currentItem = nil
|
||||
}
|
||||
|
||||
@@ -274,31 +317,47 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
func AVWrapper(didChangeState state: AVPlayerWrapperState) {
|
||||
switch state {
|
||||
case .playing, .paused: updatePlaybackValues()
|
||||
case .ready:
|
||||
if (automaticallyUpdateNowPlayingInfo) {
|
||||
updateNowPlayingPlaybackValues()
|
||||
}
|
||||
case .playing, .paused:
|
||||
if (automaticallyUpdateNowPlayingInfo) {
|
||||
updateNowPlayingCurrentTime(currentTime)
|
||||
updateNowPlayingRate(rate)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
self.event.stateChange.emit(data: state)
|
||||
self.delegate?.audioPlayer(playerDidChangeState: state)
|
||||
}
|
||||
|
||||
func AVWrapperItemDidComplete() {
|
||||
self.delegate?.audioPlayerItemDidComplete()
|
||||
}
|
||||
|
||||
func AVWrapper(secondsElapsed seconds: Double) {
|
||||
self.event.secondElapse.emit(data: seconds)
|
||||
self.delegate?.audioPlayer(secondsElapsed: seconds)
|
||||
}
|
||||
|
||||
func AVWrapper(failedWithError error: Error?) {
|
||||
self.event.fail.emit(data: error)
|
||||
self.delegate?.audioPlayer(failedWithError: error)
|
||||
}
|
||||
|
||||
func AVWrapper(seekTo seconds: Int, didFinish: Bool) {
|
||||
self.updatePlaybackValues()
|
||||
if !didFinish && automaticallyUpdateNowPlayingInfo {
|
||||
updateNowPlayingCurrentTime(currentTime)
|
||||
}
|
||||
self.event.seek.emit(data: (seconds, didFinish))
|
||||
self.delegate?.audioPlayer(seekTo: seconds, didFinish: didFinish)
|
||||
}
|
||||
|
||||
func AVWrapper(didUpdateDuration duration: Double) {
|
||||
self.event.updateDuration.emit(data: duration)
|
||||
self.delegate?.audioPlayer(didUpdateDuration: duration)
|
||||
}
|
||||
|
||||
func AVWrapperItemDidPlayToEndTime() {
|
||||
self.event.playbackEnd.emit(data: .playedUntilEnd)
|
||||
self.delegate?.audioPlayer(itemPlaybackEndedWithReason: .playedUntilEnd)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// AudioSession.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 02/11/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
|
||||
protocol AudioSession {
|
||||
|
||||
var isOtherAudioPlaying: Bool { get }
|
||||
|
||||
var category: AVAudioSession.Category { get }
|
||||
|
||||
var mode: AVAudioSession.Mode { get }
|
||||
|
||||
var categoryOptions: AVAudioSession.CategoryOptions { get }
|
||||
|
||||
var availableCategories: [AVAudioSession.Category] { get }
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, options: AVAudioSession.CategoryOptions) throws
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
func setCategory(_ category: AVAudioSession.Category, mode: AVAudioSession.Mode, policy: AVAudioSession.RouteSharingPolicy, options: AVAudioSession.CategoryOptions) throws
|
||||
|
||||
func setActive(_ active: Bool, options: AVAudioSession.SetActiveOptions) throws
|
||||
|
||||
}
|
||||
|
||||
extension AVAudioSession: AudioSession {}
|
||||
+12
-56
@@ -8,54 +8,12 @@
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
|
||||
/**
|
||||
An enum wrapper around the AVAudioSessionCategories.
|
||||
For detailed info about the categories, see: [AudioSession Programming Guide](https://developer.apple.com/library/content/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionCategoriesandModes/AudioSessionCategoriesandModes.html#//apple_ref/doc/uid/TP40007875-CH10)
|
||||
*/
|
||||
public enum AudioSessionCategory {
|
||||
|
||||
case ambient
|
||||
|
||||
case soloAmbient
|
||||
|
||||
case playback
|
||||
|
||||
case record
|
||||
|
||||
case playAndRecord
|
||||
|
||||
case multiRoute
|
||||
|
||||
func getValue() -> String {
|
||||
switch self {
|
||||
|
||||
case .ambient:
|
||||
return AVAudioSessionCategoryAmbient
|
||||
|
||||
case .soloAmbient:
|
||||
return AVAudioSessionCategorySoloAmbient
|
||||
|
||||
case .playback:
|
||||
return AVAudioSessionCategoryPlayback
|
||||
|
||||
case .record:
|
||||
return AVAudioSessionCategoryRecord
|
||||
|
||||
case .playAndRecord:
|
||||
return AVAudioSessionCategoryPlayAndRecord
|
||||
|
||||
case .multiRoute:
|
||||
return AVAudioSessionCategoryMultiRoute
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public protocol AudioSessionControllerDelegate: class {
|
||||
func handleInterruption(type: AVAudioSessionInterruptionType)
|
||||
func handleInterruption(type: AVAudioSession.InterruptionType)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Simple controller for the `AVAudioSession`. If you need more advanced options, just use the `AVAudioSession` directly.
|
||||
- warning: Do not combine usage of this and `AVAudioSession` directly, chose one.
|
||||
@@ -64,7 +22,7 @@ public class AudioSessionController {
|
||||
|
||||
public static let shared = AudioSessionController()
|
||||
|
||||
private let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
|
||||
private let audioSession: AudioSession
|
||||
private let notificationCenter: NotificationCenter = NotificationCenter.default
|
||||
private var _isObservingForInterruptions: Bool = false
|
||||
|
||||
@@ -107,13 +65,14 @@ public class AudioSessionController {
|
||||
|
||||
public weak var delegate: AudioSessionControllerDelegate?
|
||||
|
||||
private init() {
|
||||
init(audioSession: AudioSession = AVAudioSession.sharedInstance()) {
|
||||
self.audioSession = audioSession
|
||||
registerForInterruptionNotification()
|
||||
}
|
||||
|
||||
public func activateSession() throws {
|
||||
do {
|
||||
try audioSession.setActive(true)
|
||||
try audioSession.setActive(true, options: [])
|
||||
audioSessionIsActive = true
|
||||
}
|
||||
catch let error { throw error }
|
||||
@@ -121,17 +80,14 @@ public class AudioSessionController {
|
||||
|
||||
public func deactivateSession() throws {
|
||||
do {
|
||||
try audioSession.setActive(false)
|
||||
try audioSession.setActive(false, options: [])
|
||||
audioSessionIsActive = false
|
||||
}
|
||||
catch let error { throw error }
|
||||
}
|
||||
|
||||
/**
|
||||
Set the audiosession.
|
||||
*/
|
||||
public func set(category: AudioSessionCategory) throws {
|
||||
try audioSession.setCategory(category.getValue())
|
||||
public func set(category: AVAudioSession.Category) throws {
|
||||
try audioSession.setCategory(category, mode: audioSession.mode, options: audioSession.categoryOptions)
|
||||
}
|
||||
|
||||
// MARK: - Interruptions
|
||||
@@ -139,20 +95,20 @@ public class AudioSessionController {
|
||||
private func registerForInterruptionNotification() {
|
||||
notificationCenter.addObserver(self,
|
||||
selector: #selector(handleInterruption),
|
||||
name: .AVAudioSessionInterruption,
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
object: nil)
|
||||
_isObservingForInterruptions = true
|
||||
}
|
||||
|
||||
private func unregisterForInterruptionNotification() {
|
||||
notificationCenter.removeObserver(self, name: .AVAudioSessionInterruption, object: nil)
|
||||
notificationCenter.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
|
||||
_isObservingForInterruptions = false
|
||||
}
|
||||
|
||||
@objc func handleInterruption(notification: Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
|
||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Event.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 09/03/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension AudioPlayer {
|
||||
|
||||
public typealias StateChangeEventData = (AudioPlayerState)
|
||||
public typealias PlaybackEndEventData = (PlaybackEndedReason)
|
||||
public typealias SecondElapseEventData = (TimeInterval)
|
||||
public typealias FailEventData = (Error?)
|
||||
public typealias SeekEventData = (seconds: Int, didFinish: Bool)
|
||||
public typealias UpdateDurationEventData = (Double)
|
||||
|
||||
public struct EventHolder {
|
||||
|
||||
/**
|
||||
Emitted when the `AudioPlayer`s state is changed
|
||||
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
|
||||
*/
|
||||
public let stateChange: AudioPlayer.Event<StateChangeEventData> = AudioPlayer.Event()
|
||||
|
||||
/**
|
||||
Emitted when the playback of the player, for some reason, has stopped.
|
||||
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
|
||||
*/
|
||||
public let playbackEnd: AudioPlayer.Event<PlaybackEndEventData> = AudioPlayer.Event()
|
||||
|
||||
/**
|
||||
Emitted when a second is elapsed in the `AudioPlayer`.
|
||||
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
|
||||
*/
|
||||
public let secondElapse: AudioPlayer.Event<SecondElapseEventData> = AudioPlayer.Event()
|
||||
|
||||
/**
|
||||
Emitted when the player encounters an error.
|
||||
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
|
||||
*/
|
||||
public let fail: AudioPlayer.Event<FailEventData> = AudioPlayer.Event()
|
||||
|
||||
/**
|
||||
Emitted when the player is done attempting to seek.
|
||||
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
|
||||
*/
|
||||
public let seek: AudioPlayer.Event<SeekEventData> = AudioPlayer.Event()
|
||||
|
||||
/**
|
||||
Emitted when the player updates its duration.
|
||||
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
|
||||
*/
|
||||
public let updateDuration: AudioPlayer.Event<UpdateDurationEventData> = AudioPlayer.Event()
|
||||
|
||||
}
|
||||
|
||||
public typealias EventClosure<EventData> = (EventData) -> Void
|
||||
|
||||
class Invoker<EventData> {
|
||||
|
||||
// Signals false if the listener object is nil
|
||||
let invoke: (EventData) -> Bool
|
||||
weak var listener: AnyObject?
|
||||
|
||||
init<Listener: AnyObject>(listener: Listener, closure: @escaping EventClosure<EventData>) {
|
||||
self.listener = listener
|
||||
self.invoke = { [weak listener] (data: EventData) in
|
||||
guard let _ = listener else {
|
||||
return false
|
||||
}
|
||||
closure(data)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Event<EventData> {
|
||||
|
||||
private let eventQueue: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.utility)
|
||||
private let actionQueue: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)
|
||||
private let invokersSemaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
|
||||
|
||||
var invokers: [Invoker<EventData>] = []
|
||||
|
||||
public func addListener<Listener: AnyObject>(_ listener: Listener, _ closure: @escaping EventClosure<EventData>) {
|
||||
actionQueue.async {
|
||||
self.invokersSemaphore.wait()
|
||||
self.invokers.append(Invoker(listener: listener, closure: closure))
|
||||
self.invokersSemaphore.signal()
|
||||
}
|
||||
}
|
||||
|
||||
public func removeListener(_ listener: AnyObject) {
|
||||
actionQueue.async {
|
||||
self.invokersSemaphore.wait()
|
||||
self.invokers = self.invokers.filter({ (invoker) -> Bool in
|
||||
if let listenerToCheck = invoker.listener {
|
||||
return listenerToCheck !== listener
|
||||
}
|
||||
return true
|
||||
})
|
||||
self.invokersSemaphore.signal()
|
||||
}
|
||||
}
|
||||
|
||||
func emit(data: EventData) {
|
||||
eventQueue.async {
|
||||
self.invokersSemaphore.wait()
|
||||
self.invokers = self.invokers.filter({ (invoker) -> Bool in
|
||||
return invoker.invoke(data)
|
||||
})
|
||||
self.invokersSemaphore.signal()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
//
|
||||
// MediaInfoController.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 15/03/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
|
||||
public protocol NowPlayingInfoKeyValue {
|
||||
func getKey() -> String
|
||||
func getValue() -> Any?
|
||||
}
|
||||
|
||||
/**
|
||||
Wrapper class to control the NowPlayingInfoCenter
|
||||
*/
|
||||
public class NowPlayingInfoController {
|
||||
|
||||
let infoCenter: MPNowPlayingInfoCenter
|
||||
|
||||
var info: [String: Any]
|
||||
|
||||
public init(infoCenter: MPNowPlayingInfoCenter) {
|
||||
self.infoCenter = infoCenter
|
||||
self.info = [:]
|
||||
}
|
||||
|
||||
/**
|
||||
This updates a set of values in the now playing info.
|
||||
|
||||
- Warning: This will reset the now playing info completely! Use this function when starting playback of a new item.
|
||||
*/
|
||||
public func set(keyValues: [NowPlayingInfoKeyValue]) {
|
||||
self.info = [:]
|
||||
keyValues.forEach { (keyValue) in
|
||||
info[keyValue.getKey()] = keyValue.getValue()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This updates a single value in the now playing info.
|
||||
*/
|
||||
public func set(keyValue: NowPlayingInfoKeyValue) {
|
||||
info[keyValue.getKey()] = keyValue.getValue()
|
||||
self.infoCenter.nowPlayingInfo = info
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// NowPlayingInfoCenter.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 03/03/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
public protocol NowPlayingInfoCenter {
|
||||
|
||||
var nowPlayingInfo: [String: Any]? { get set }
|
||||
|
||||
}
|
||||
|
||||
extension MPNowPlayingInfoCenter: NowPlayingInfoCenter {}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// MediaInfoController.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 15/03/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
|
||||
public class NowPlayingInfoController: NowPlayingInfoControllerProtocol {
|
||||
|
||||
private var _infoCenter: NowPlayingInfoCenter
|
||||
private var _info: [String: Any] = [:]
|
||||
|
||||
var infoCenter: NowPlayingInfoCenter {
|
||||
return _infoCenter
|
||||
}
|
||||
|
||||
var info: [String: Any] {
|
||||
return _info
|
||||
}
|
||||
|
||||
public required init() {
|
||||
self._infoCenter = MPNowPlayingInfoCenter.default()
|
||||
}
|
||||
|
||||
public required init(infoCenter: NowPlayingInfoCenter) {
|
||||
self._infoCenter = infoCenter
|
||||
}
|
||||
|
||||
public func set(keyValues: [NowPlayingInfoKeyValue]) {
|
||||
keyValues.forEach { (keyValue) in
|
||||
_info[keyValue.getKey()] = keyValue.getValue()
|
||||
}
|
||||
self._infoCenter.nowPlayingInfo = _info
|
||||
}
|
||||
|
||||
public func set(keyValue: NowPlayingInfoKeyValue) {
|
||||
_info[keyValue.getKey()] = keyValue.getValue()
|
||||
self._infoCenter.nowPlayingInfo = _info
|
||||
}
|
||||
|
||||
public func clear() {
|
||||
self._info = [:]
|
||||
self._infoCenter.nowPlayingInfo = _info
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// NowPlayingInfoControllerProtocol.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 28/02/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
|
||||
public protocol NowPlayingInfoControllerProtocol {
|
||||
|
||||
init()
|
||||
|
||||
init(infoCenter: NowPlayingInfoCenter)
|
||||
|
||||
func set(keyValue: NowPlayingInfoKeyValue)
|
||||
|
||||
func set(keyValues: [NowPlayingInfoKeyValue])
|
||||
|
||||
func clear()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// NowPlayingInfoKeyValue.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 28/02/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public protocol NowPlayingInfoKeyValue {
|
||||
func getKey() -> String
|
||||
func getValue() -> Any?
|
||||
}
|
||||
@@ -27,6 +27,7 @@ class AVPlayerItemObserver: NSObject {
|
||||
|
||||
private struct AVPlayerItemKeyPath {
|
||||
static let duration = #keyPath(AVPlayerItem.duration)
|
||||
static let loadedTimeRanges = #keyPath(AVPlayerItem.loadedTimeRanges)
|
||||
}
|
||||
|
||||
var isObserving: Bool = false
|
||||
@@ -53,11 +54,13 @@ class AVPlayerItemObserver: NSObject {
|
||||
self.isObserving = true
|
||||
self.observingItem = item
|
||||
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, options: [.new], context: &AVPlayerItemObserver.context)
|
||||
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.loadedTimeRanges, options: [.new], context: &AVPlayerItemObserver.context)
|
||||
}
|
||||
}
|
||||
|
||||
private func stopObservingCurrentItem() {
|
||||
func stopObservingCurrentItem() {
|
||||
observingItem?.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, context: &AVPlayerItemObserver.context)
|
||||
observingItem?.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.loadedTimeRanges, context: &AVPlayerItemObserver.context)
|
||||
self.isObserving = false
|
||||
self.observingItem = nil
|
||||
}
|
||||
@@ -73,8 +76,12 @@ class AVPlayerItemObserver: NSObject {
|
||||
if let duration = change?[.newKey] as? CMTime {
|
||||
self.delegate?.item(didUpdateDuration: duration.seconds)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
||||
case AVPlayerItemKeyPath.loadedTimeRanges:
|
||||
if let ranges = change?[.newKey] as? [NSValue], let duration = ranges.first?.timeRangeValue.duration {
|
||||
self.delegate?.item(didUpdateDuration: duration.seconds)
|
||||
}
|
||||
default: break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ protocol AVPlayerObserverDelegate: class {
|
||||
/**
|
||||
Called when the AVPlayer.status changes.
|
||||
*/
|
||||
func player(statusDidChange status: AVPlayerStatus)
|
||||
func player(statusDidChange status: AVPlayer.Status)
|
||||
|
||||
/**
|
||||
Called when the AVPlayer.timeControlStatus changes.
|
||||
*/
|
||||
func player(didChangeTimeControlStatus status: AVPlayerTimeControlStatus)
|
||||
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus)
|
||||
|
||||
}
|
||||
|
||||
@@ -92,9 +92,9 @@ class AVPlayerObserver: NSObject {
|
||||
}
|
||||
|
||||
private func handleStatusChange(_ change: [NSKeyValueChangeKey: Any]?) {
|
||||
let status: AVPlayerStatus
|
||||
let status: AVPlayer.Status
|
||||
if let statusNumber = change?[.newKey] as? NSNumber {
|
||||
status = AVPlayerStatus(rawValue: statusNumber.intValue)!
|
||||
status = AVPlayer.Status(rawValue: statusNumber.intValue)!
|
||||
}
|
||||
else {
|
||||
status = .unknown
|
||||
@@ -104,9 +104,9 @@ class AVPlayerObserver: NSObject {
|
||||
|
||||
private func handleTimeControlStatusChange(_ change: [NSKeyValueChangeKey: Any]?) {
|
||||
|
||||
let status: AVPlayerTimeControlStatus
|
||||
let status: AVPlayer.TimeControlStatus
|
||||
if let statusNumber = change?[.newKey] as? NSNumber {
|
||||
status = AVPlayerTimeControlStatus(rawValue: statusNumber.intValue)!
|
||||
status = AVPlayer.TimeControlStatus(rawValue: statusNumber.intValue)!
|
||||
delegate?.player(didChangeTimeControlStatus: status)
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+72
-10
@@ -20,10 +20,10 @@ class QueueManager<T> {
|
||||
}
|
||||
|
||||
public var nextItems: [T] {
|
||||
guard _currentIndex < _items.count else {
|
||||
guard _currentIndex + 1 < _items.count else {
|
||||
return []
|
||||
}
|
||||
return Array(_items[_currentIndex + 1..<items.count])
|
||||
return Array(_items[_currentIndex + 1..<_items.count])
|
||||
}
|
||||
|
||||
public var previousItems: [T] {
|
||||
@@ -71,6 +71,21 @@ class QueueManager<T> {
|
||||
_items.append(contentsOf: items)
|
||||
}
|
||||
|
||||
/**
|
||||
Add an array of items to the queue at a given index.
|
||||
|
||||
- parameter items: The `AudioItem`s to be added.
|
||||
- parameter at: The index to insert the items at.
|
||||
*/
|
||||
public func addItems(_ items: [T], at index: Int) throws {
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for addition has to be positive and smaller than the count of current items (\(_items.count))")
|
||||
}
|
||||
|
||||
_items.insert(contentsOf: items, at: index)
|
||||
if (_currentIndex >= index) { _currentIndex = _currentIndex + items.count }
|
||||
}
|
||||
|
||||
/**
|
||||
Get the next item in the queue, if there are any.
|
||||
Will update the current item.
|
||||
@@ -119,9 +134,10 @@ class QueueManager<T> {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Cannot jump to the current item")
|
||||
}
|
||||
|
||||
guard index >= 0 && items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "The jump index has to be positive and smaller thant the count of current items (\(items.count))")
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "The jump index has to be positive and smaller thant the count of current items (\(_items.count))")
|
||||
}
|
||||
|
||||
_currentIndex = index
|
||||
return _items[index]
|
||||
}
|
||||
@@ -140,14 +156,15 @@ class QueueManager<T> {
|
||||
}
|
||||
|
||||
guard fromIndex >= 0 && fromIndex < _items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: fromIndex, message: "The fromIndex has to be positive and smaller than the count of current items (\(items.count)).")
|
||||
throw APError.QueueError.invalidIndex(index: fromIndex, message: "The fromIndex has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
}
|
||||
|
||||
guard toIndex >= 0 && toIndex < _items.count else {
|
||||
throw APError.QueueError.invalidIndex(index: toIndex, message: "The toIndex has to be positive and smaller than the count of current items (\(items.count)).")
|
||||
throw APError.QueueError.invalidIndex(index: toIndex, message: "The toIndex has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
}
|
||||
|
||||
_items.insert(_items.remove(at: fromIndex), at: toIndex)
|
||||
let item = try removeItem(at: fromIndex)
|
||||
try addItems([item], at: toIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,16 +175,61 @@ class QueueManager<T> {
|
||||
- returns: The removed item.
|
||||
*/
|
||||
@discardableResult
|
||||
public func remove(atIndex index: Int) throws -> T {
|
||||
public func removeItem(at index: Int) throws -> T {
|
||||
guard index != _currentIndex else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Cannot remove the current item!")
|
||||
}
|
||||
|
||||
guard index >= 0 && _items.count > index else {
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for removal has to be postivie and smaller than the count of current items (\(items.count)).")
|
||||
throw APError.QueueError.invalidIndex(index: index, message: "Index for removal has to be positive and smaller than the count of current items (\(_items.count)).")
|
||||
}
|
||||
|
||||
if index < _currentIndex {
|
||||
_currentIndex = _currentIndex - 1
|
||||
}
|
||||
|
||||
return _items.remove(at: index)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Replace the current item with a new one. If there is no current item, it is equivalent to calling add(item:).
|
||||
|
||||
- parameter item: The item to set as the new current item.
|
||||
*/
|
||||
public func replaceCurrentItem(with item: T) {
|
||||
if current == nil {
|
||||
self.addItem(item)
|
||||
}
|
||||
|
||||
self._items[_currentIndex] = item
|
||||
}
|
||||
|
||||
/**
|
||||
Remove all previous items in the queue.
|
||||
If no previous items exist, no action will be taken.
|
||||
*/
|
||||
public func removePreviousItems() {
|
||||
guard currentIndex > 0 else { return }
|
||||
_items.removeSubrange(0..<_currentIndex)
|
||||
_currentIndex = 0
|
||||
}
|
||||
|
||||
/**
|
||||
Remove upcoming items.
|
||||
If no upcoming items exist, no action will be taken.
|
||||
*/
|
||||
public func removeUpcomingItems() {
|
||||
let nextIndex = _currentIndex + 1
|
||||
guard nextIndex < _items.count else { return }
|
||||
_items.removeSubrange(nextIndex..<_items.count)
|
||||
}
|
||||
|
||||
/**
|
||||
Removes all items for queue
|
||||
*/
|
||||
public func clearQueue() {
|
||||
_currentIndex = 0
|
||||
_items.removeAll()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+55
-13
@@ -21,14 +21,21 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
*/
|
||||
public var automaticallyPlayNextSong: Bool = true
|
||||
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
super.init(infoCenter: infoCenter)
|
||||
}
|
||||
|
||||
public override var currentItem: AudioItem? {
|
||||
return queueManager.current
|
||||
}
|
||||
|
||||
/**
|
||||
Stops the player and clears the queue.
|
||||
*/
|
||||
public override func stop() {
|
||||
super.stop()
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
queueManager.clearQueue()
|
||||
}
|
||||
|
||||
/**
|
||||
The previous items held by the queue.
|
||||
*/
|
||||
@@ -43,6 +50,17 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
return queueManager.nextItems
|
||||
}
|
||||
|
||||
/**
|
||||
Will replace the current item with a new one and load it into the player.
|
||||
|
||||
- parameter item: The AudioItem to replace the current item.
|
||||
- throws: APError.LoadError
|
||||
*/
|
||||
public override func load(item: AudioItem, playWhenReady: Bool) throws {
|
||||
try super.load(item: item, playWhenReady: playWhenReady)
|
||||
queueManager.replaceCurrentItem(with: item)
|
||||
}
|
||||
|
||||
/**
|
||||
Add a single item to the queue.
|
||||
|
||||
@@ -53,7 +71,7 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
public func add(item: AudioItem, playWhenReady: Bool = true) throws {
|
||||
if currentItem == nil {
|
||||
queueManager.addItem(item)
|
||||
try self.loadItem(item, playWhenReady: playWhenReady)
|
||||
try self.load(item: item, playWhenReady: playWhenReady)
|
||||
}
|
||||
else {
|
||||
queueManager.addItem(item)
|
||||
@@ -70,29 +88,37 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
public func add(items: [AudioItem], playWhenReady: Bool = true) throws {
|
||||
if currentItem == nil {
|
||||
queueManager.addItems(items)
|
||||
try self.loadItem(currentItem!, playWhenReady: playWhenReady)
|
||||
try self.load(item: currentItem!, playWhenReady: playWhenReady)
|
||||
}
|
||||
else {
|
||||
queueManager.addItems(items)
|
||||
}
|
||||
}
|
||||
|
||||
public func add(items: [AudioItem], at index: Int) throws {
|
||||
try queueManager.addItems(items, at: index)
|
||||
}
|
||||
|
||||
/**
|
||||
Step to the next item in the queue.
|
||||
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func next() throws {
|
||||
event.playbackEnd.emit(data: .skippedToNext)
|
||||
delegate?.audioPlayer(itemPlaybackEndedWithReason: .skippedToNext)
|
||||
let nextItem = try queueManager.next()
|
||||
try self.loadItem(nextItem, playWhenReady: true)
|
||||
try self.load(item: nextItem, playWhenReady: true)
|
||||
}
|
||||
|
||||
/**
|
||||
Step to the previous item in the queue.
|
||||
*/
|
||||
public func previous() throws {
|
||||
event.playbackEnd.emit(data: .skippedToPrevious)
|
||||
delegate?.audioPlayer(itemPlaybackEndedWithReason: .skippedToPrevious)
|
||||
let previousItem = try queueManager.previous()
|
||||
try self.loadItem(previousItem, playWhenReady: true)
|
||||
try self.load(item: previousItem, playWhenReady: true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,8 +127,8 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
- parameter index: The index of the item to remove.
|
||||
- throws: `APError.QueueError`
|
||||
*/
|
||||
public func removeItem(atIndex index: Int) throws {
|
||||
try queueManager.remove(atIndex: index)
|
||||
public func removeItem(at index: Int) throws {
|
||||
try queueManager.removeItem(at: index)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,8 +139,10 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
- throws: `APError`
|
||||
*/
|
||||
public func jumpToItem(atIndex index: Int, playWhenReady: Bool = true) throws {
|
||||
event.playbackEnd.emit(data: .jumpedToIndex)
|
||||
delegate?.audioPlayer(itemPlaybackEndedWithReason: .jumpedToIndex)
|
||||
let item = try queueManager.jump(to: index)
|
||||
try self.loadItem(item, playWhenReady: playWhenReady)
|
||||
try self.load(item: item, playWhenReady: playWhenReady)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,10 +156,24 @@ public class QueuedAudioPlayer: AudioPlayer {
|
||||
try queueManager.moveItem(fromIndex: fromIndex, toIndex: toIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
Remove all upcoming items, those returned by `next()`
|
||||
*/
|
||||
public func removeUpcomingItems() {
|
||||
queueManager.removeUpcomingItems()
|
||||
}
|
||||
|
||||
/**
|
||||
Remove all previous items, those returned by `previous()`
|
||||
*/
|
||||
public func removePreviousItems() {
|
||||
queueManager.removePreviousItems()
|
||||
}
|
||||
|
||||
// MARK: - AVPlayerWrapperDelegate
|
||||
|
||||
override func AVWrapperItemDidComplete() {
|
||||
super.AVWrapperItemDidComplete()
|
||||
override func AVWrapperItemDidPlayToEndTime() {
|
||||
super.AVWrapperItemDidPlayToEndTime()
|
||||
if automaticallyPlayNextSong {
|
||||
try? self.next()
|
||||
}
|
||||
|
||||
+41
-66
@@ -14,27 +14,29 @@ public protocol RemoteCommandable {
|
||||
|
||||
public class RemoteCommandController {
|
||||
|
||||
private let center = MPRemoteCommandCenter.shared()
|
||||
private let center: MPRemoteCommandCenter
|
||||
|
||||
weak var audioPlayer: AudioPlayer?
|
||||
|
||||
var commandTargetPointers: [String: Any] = [:]
|
||||
|
||||
init() {}
|
||||
|
||||
/**
|
||||
Enable a set of RemoteCommands. Calling this will disable all earlier set commands, so include all commands that needs to be active.
|
||||
Create a new RemoteCommandController.
|
||||
|
||||
- parameter commands: The RemoteCommands that is to be enabled.
|
||||
- parameter remoteCommandCenter: The MPRemoteCommandCenter used. Default is `MPRemoteCommandCenter.shared()`
|
||||
*/
|
||||
public func enable(commands: [RemoteCommand]) {
|
||||
public init(remoteCommandCenter: MPRemoteCommandCenter = MPRemoteCommandCenter.shared()) {
|
||||
self.center = remoteCommandCenter
|
||||
}
|
||||
|
||||
internal func enable(commands: [RemoteCommand]) {
|
||||
self.disable(commands: RemoteCommand.all())
|
||||
commands.forEach { (command) in
|
||||
self.enable(command: command)
|
||||
}
|
||||
}
|
||||
|
||||
private func disable(commands: [RemoteCommand]) {
|
||||
internal func disable(commands: [RemoteCommand]) {
|
||||
commands.forEach { (command) in
|
||||
self.disable(command: command)
|
||||
}
|
||||
@@ -62,7 +64,6 @@ public class RemoteCommandController {
|
||||
case .changePlaybackPosition: self.enableCommand(ChangePlaybackPositionCommand.changePlaybackPosition)
|
||||
case .skipForward(let preferredIntervals): self.enableCommand(SkipIntervalCommand.skipForward.set(preferredIntervals: preferredIntervals))
|
||||
case .skipBackward(let preferredIntervals): self.enableCommand(SkipIntervalCommand.skipBackward.set(preferredIntervals: preferredIntervals))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,33 +83,33 @@ public class RemoteCommandController {
|
||||
|
||||
// MARK: - Handlers
|
||||
|
||||
lazy var handlePlayCommand: RemoteCommandHandler = { (event) in
|
||||
public lazy var handlePlayCommand: RemoteCommandHandler = self.handlePlayCommandDefault
|
||||
public lazy var handlePauseCommand: RemoteCommandHandler = self.handlePauseCommandDefault
|
||||
public lazy var handleStopCommand: RemoteCommandHandler = self.handleStopCommandDefault
|
||||
public lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = self.handleTogglePlayPauseCommandDefault
|
||||
public lazy var handleSkipForwardCommand: RemoteCommandHandler = self.handleSkipForwardCommandDefault
|
||||
public lazy var handleSkipBackwardCommand: RemoteCommandHandler = self.handleSkipBackwardDefault
|
||||
public lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = self.handleChangePlaybackPositionCommandDefault
|
||||
public lazy var handleNextTrackCommand: RemoteCommandHandler = self.handleNextTrackCommandDefault
|
||||
public lazy var handlePreviousTrackCommand: RemoteCommandHandler = self.handlePreviousTrackCommandDefault
|
||||
|
||||
private func handlePlayCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.play()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
audioPlayer.play()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handlePauseCommand: RemoteCommandHandler = { (event) in
|
||||
private func handlePauseCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.pause()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
audioPlayer.pause()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleStopCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleStopCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
audioPlayer.stop()
|
||||
return .success
|
||||
@@ -116,64 +117,44 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleTogglePlayPauseCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.togglePlaying()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
audioPlayer.togglePlaying()
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleSkipForwardCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleSkipForwardCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.seek(to: audioPlayer.currentTime + Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
audioPlayer.seek(to: audioPlayer.currentTime + Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleSkipBackwardCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleSkipBackwardDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let command = event.command as? MPSkipIntervalCommand,
|
||||
let interval = command.preferredIntervals.first,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.seek(to: audioPlayer.currentTime - Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
audioPlayer.seek(to: audioPlayer.currentTime - Double(truncating: interval))
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleChangePlaybackPositionCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let event = event as? MPChangePlaybackPositionCommandEvent,
|
||||
let audioPlayer = self.audioPlayer {
|
||||
do {
|
||||
try audioPlayer.seek(to: event.positionTime)
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
catch let error {
|
||||
return self.getRemoteCommandHandlerStatus(forError: error)
|
||||
}
|
||||
audioPlayer.seek(to: event.positionTime)
|
||||
return MPRemoteCommandHandlerStatus.success
|
||||
}
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handleNextTrackCommand: RemoteCommandHandler = { (event) in
|
||||
private func handleNextTrackCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.next()
|
||||
@@ -186,7 +167,7 @@ public class RemoteCommandController {
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
}
|
||||
|
||||
lazy var handlePreviousTrackCommand: RemoteCommandHandler = { (event) in
|
||||
private func handlePreviousTrackCommandDefault(event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
|
||||
if let player = self.audioPlayer as? QueuedAudioPlayer {
|
||||
do {
|
||||
try player.previous()
|
||||
@@ -200,13 +181,7 @@ public class RemoteCommandController {
|
||||
}
|
||||
|
||||
private func getRemoteCommandHandlerStatus(forError error: Error) -> MPRemoteCommandHandlerStatus {
|
||||
if let error = error as? APError.PlaybackError {
|
||||
switch error {
|
||||
case .noLoadedItem:
|
||||
return MPRemoteCommandHandlerStatus.noActionableNowPlayingItem
|
||||
}
|
||||
}
|
||||
else if let error = error as? APError.LoadError {
|
||||
if let error = error as? APError.LoadError {
|
||||
switch error {
|
||||
case .invalidSourceUrl(_):
|
||||
return MPRemoteCommandHandlerStatus.commandFailed
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// SimpleAudioPlayer.swift
|
||||
// SwiftAudio
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 24/03/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MediaPlayer
|
||||
|
||||
/**
|
||||
A simple audio player that keeps on item at a time.
|
||||
*/
|
||||
public class SimpleAudioPlayer: AudioPlayer {
|
||||
|
||||
public override init(infoCenter: MPNowPlayingInfoCenter = MPNowPlayingInfoCenter.default()) {
|
||||
super.init(infoCenter: infoCenter)
|
||||
}
|
||||
|
||||
/**
|
||||
Load an AudioItem into the manager.
|
||||
|
||||
- parameter item: The AudioItem to load. The info given in this item is the one used for the InfoCenter.
|
||||
- parameter playWhenReady: Immediately start playback when the item is ready. Default is `true`. If you disable this you have to call play() or togglePlay() when the `state` switches to `ready`.
|
||||
*/
|
||||
public func load(item: AudioItem, playWhenReady: Bool = true) throws {
|
||||
try self.loadItem(item, playWhenReady: playWhenReady)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user