Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fb762db9c | |||
| 81ce63752d | |||
| c03c83096e | |||
| 0c87d2479e | |||
| 98f3646e84 | |||
| 9ebbd99230 |
@@ -21,7 +21,6 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: Run Tests
|
||||
run: |-
|
||||
cd Example
|
||||
xcodebuild test -scheme SwiftAudio-Example -destination "${destination}" -enableCodeCoverage YES
|
||||
xcodebuild test -scheme SwiftAudioEx -destination "${destination}" -enableCodeCoverage YES
|
||||
env:
|
||||
destination: ${{ matrix.destination }}
|
||||
|
||||
@@ -12,80 +12,20 @@
|
||||
0707130B2067F2E000F789B3 /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0707130A2067F2E000F789B3 /* QueueViewController.swift */; };
|
||||
0707130F2067F40A00F789B3 /* QueueTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0707130D2067F40A00F789B3 /* QueueTableViewCell.swift */; };
|
||||
070713102067F40A00F789B3 /* QueueTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0707130E2067F40A00F789B3 /* QueueTableViewCell.xib */; };
|
||||
0708ED6C2116DA4C00EB29BD /* AudioSessionControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */; };
|
||||
0708ED702116E89900EB29BD /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED6F2116E89900EB29BD /* Source.swift */; };
|
||||
0708ED722116E91D00EB29BD /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED6F2116E89900EB29BD /* Source.swift */; };
|
||||
0708ED742116EE0100EB29BD /* AudioPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */; };
|
||||
0708ED79211732F500EB29BD /* TestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 0708ED78211732F500EB29BD /* TestSound.m4a */; };
|
||||
0708ED7A211732F500EB29BD /* TestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 0708ED78211732F500EB29BD /* TestSound.m4a */; };
|
||||
07194D212127F6DB002EA8C8 /* ShortTestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */; };
|
||||
07194D222127F6E9002EA8C8 /* ShortTestSound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
|
||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
|
||||
607FACEC1AFB9204008FA782 /* AVPlayerObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */; };
|
||||
9B05AA312660276400C7A389 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA302660276400C7A389 /* Quick */; };
|
||||
9B05AA332660276400C7A389 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = 9B05AA322660276400C7A389 /* Nimble */; };
|
||||
9B1D5E1E27C76F5C004CA883 /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B1D5E1D27C76F5C004CA883 /* SwiftAudioEx */; };
|
||||
9B1D5E2027C76F6F004CA883 /* SwiftAudioEx in Frameworks */ = {isa = PBXBuildFile; productRef = 9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */; };
|
||||
9B521D0E2662937600EF0C3A /* MockDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B521D0D2662937600EF0C3A /* MockDispatchQueue.swift */; };
|
||||
F048FE7728D215A9001AA2AB /* five_seconds.m4a in Resources */ = {isa = PBXBuildFile; fileRef = F048FE7628D215A9001AA2AB /* five_seconds.m4a */; };
|
||||
F048FE7828D215A9001AA2AB /* five_seconds.m4a in Resources */ = {isa = PBXBuildFile; fileRef = F048FE7628D215A9001AA2AB /* five_seconds.m4a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 607FACCF1AFB9204008FA782;
|
||||
remoteInfo = SwiftAudio;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
070713062067EB4F00F789B3 /* Double + Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double + Extensions.swift"; sourceTree = "<group>"; };
|
||||
070713082067EFFB00F789B3 /* AudioController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioController.swift; sourceTree = "<group>"; };
|
||||
0707130A2067F2E000F789B3 /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
|
||||
0707130D2067F40A00F789B3 /* QueueTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTableViewCell.swift; sourceTree = "<group>"; };
|
||||
0707130E2067F40A00F789B3 /* QueueTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QueueTableViewCell.xib; sourceTree = "<group>"; };
|
||||
0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionControllerTests.swift; sourceTree = "<group>"; };
|
||||
0708ED6F2116E89900EB29BD /* Source.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Source.swift; sourceTree = "<group>"; };
|
||||
0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerTests.swift; sourceTree = "<group>"; };
|
||||
0708ED78211732F500EB29BD /* TestSound.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestSound.m4a; sourceTree = "<group>"; };
|
||||
07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = ShortTestSound.m4a; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -93,12 +33,7 @@
|
||||
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
607FACE51AFB9204008FA782 /* SwiftAudio_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftAudio_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerObserverTests.swift; sourceTree = "<group>"; };
|
||||
9B1D5E1C27C76F49004CA883 /* SwiftAudioEx */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftAudioEx; path = ..; sourceTree = "<group>"; };
|
||||
9B521D0D2662937600EF0C3A /* MockDispatchQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDispatchQueue.swift; sourceTree = "<group>"; };
|
||||
F048FE7628D215A9001AA2AB /* five_seconds.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = five_seconds.m4a; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -110,48 +45,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
607FACE21AFB9204008FA782 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9B1D5E1E27C76F5C004CA883 /* SwiftAudioEx in Frameworks */,
|
||||
9B05AA312660276400C7A389 /* Quick in Frameworks */,
|
||||
9B05AA332660276400C7A389 /* Nimble in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0708ED712116E91300EB29BD /* Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F048FE7628D215A9001AA2AB /* five_seconds.m4a */,
|
||||
07194D1F2127F283002EA8C8 /* ShortTestSound.m4a */,
|
||||
0708ED6F2116E89900EB29BD /* Source.swift */,
|
||||
07732650205EACA300C4D1CD /* WAV-MP3.wav */,
|
||||
07732652205EB1B500C4D1CD /* nasa_throttle_up.mp3 */,
|
||||
0708ED78211732F500EB29BD /* TestSound.m4a */,
|
||||
);
|
||||
path = Source;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
07756B67218A4E640023935E /* Mocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
07756B68218A4E870023935E /* AudioSession.swift */,
|
||||
074B0D6A222C247B001A45A9 /* NowPlayingInfoCenter.swift */,
|
||||
074B0D6C222C24DE001A45A9 /* NowPlayingInfoController.swift */,
|
||||
9B521D0D2662937600EF0C3A /* MockDispatchQueue.swift */,
|
||||
);
|
||||
path = Mocks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACC71AFB9204008FA782 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD21AFB9204008FA782 /* Example for SwiftAudio */,
|
||||
607FACE81AFB9204008FA782 /* Tests */,
|
||||
607FACD11AFB9204008FA782 /* Products */,
|
||||
9B05AA2F2660276400C7A389 /* Frameworks */,
|
||||
);
|
||||
@@ -161,7 +61,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */,
|
||||
607FACE51AFB9204008FA782 /* SwiftAudio_Tests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -193,36 +92,6 @@
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACE81AFB9204008FA782 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
07756B67218A4E640023935E /* Mocks */,
|
||||
0708ED732116EE0100EB29BD /* AudioPlayerTests.swift */,
|
||||
607FACEB1AFB9204008FA782 /* AVPlayerObserverTests.swift */,
|
||||
074A6482205C155E0083D868 /* AVPlayerTimeObserverTests.swift */,
|
||||
074A6484205C29920083D868 /* AVPlayerItemNotificationObserverTests.swift */,
|
||||
074A6486205E59B60083D868 /* AVPlayerWrapperTests.swift */,
|
||||
0775575820668B020002C6A1 /* QueueManagerTests.swift */,
|
||||
078C908D210D25F700555E80 /* AVPlayerItemObserverTests.swift */,
|
||||
0708ED6B2116DA4B00EB29BD /* AudioSessionControllerTests.swift */,
|
||||
07DBB1E0212C17E600BB4278 /* QueuedAudioPlayerTests.swift */,
|
||||
07EB8EE022286980000197DE /* NowPlayingInfoTests.swift */,
|
||||
074B0D66222C1EC7001A45A9 /* NowPlayingInfoControllerTests.swift */,
|
||||
076DFC5E22345EAF00A8D163 /* AudioPlayerEventTests.swift */,
|
||||
0708ED712116E91300EB29BD /* Source */,
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
607FACEA1AFB9204008FA782 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9B05AA2F2660276400C7A389 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -254,29 +123,6 @@
|
||||
productReference = 607FACD01AFB9204008FA782 /* SwiftAudio_Example.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
607FACE41AFB9204008FA782 /* SwiftAudio_Tests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAudio_Tests" */;
|
||||
buildPhases = (
|
||||
607FACE11AFB9204008FA782 /* Sources */,
|
||||
607FACE21AFB9204008FA782 /* Frameworks */,
|
||||
607FACE31AFB9204008FA782 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
607FACE71AFB9204008FA782 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SwiftAudio_Tests;
|
||||
packageProductDependencies = (
|
||||
9B05AA302660276400C7A389 /* Quick */,
|
||||
9B05AA322660276400C7A389 /* Nimble */,
|
||||
9B1D5E1D27C76F5C004CA883 /* SwiftAudioEx */,
|
||||
);
|
||||
productName = Tests;
|
||||
productReference = 607FACE51AFB9204008FA782 /* SwiftAudio_Tests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -296,11 +142,6 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
607FACE41AFB9204008FA782 = {
|
||||
CreatedOnToolsVersion = 6.3.1;
|
||||
LastSwiftMigration = 1020;
|
||||
TestTargetID = 607FACCF1AFB9204008FA782;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAudio" */;
|
||||
@@ -313,15 +154,12 @@
|
||||
);
|
||||
mainGroup = 607FACC71AFB9204008FA782;
|
||||
packageReferences = (
|
||||
9B05AA292660273200C7A389 /* XCRemoteSwiftPackageReference "Quick" */,
|
||||
9B05AA2C2660274F00C7A389 /* XCRemoteSwiftPackageReference "Nimble" */,
|
||||
);
|
||||
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
607FACCF1AFB9204008FA782 /* SwiftAudio_Example */,
|
||||
607FACE41AFB9204008FA782 /* SwiftAudio_Tests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -333,28 +171,11 @@
|
||||
files = (
|
||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
|
||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
|
||||
07732655205ECE1C00C4D1CD /* nasa_throttle_up.mp3 in Resources */,
|
||||
07194D222127F6E9002EA8C8 /* ShortTestSound.m4a in Resources */,
|
||||
F048FE7728D215A9001AA2AB /* five_seconds.m4a in Resources */,
|
||||
0708ED79211732F500EB29BD /* TestSound.m4a in Resources */,
|
||||
070713102067F40A00F789B3 /* QueueTableViewCell.xib in Resources */,
|
||||
07732654205ECA8B00C4D1CD /* WAV-MP3.wav in Resources */,
|
||||
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
607FACE31AFB9204008FA782 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
07194D212127F6DB002EA8C8 /* ShortTestSound.m4a in Resources */,
|
||||
0708ED7A211732F500EB29BD /* TestSound.m4a in Resources */,
|
||||
07732653205EB1B500C4D1CD /* nasa_throttle_up.mp3 in Resources */,
|
||||
F048FE7828D215A9001AA2AB /* five_seconds.m4a in Resources */,
|
||||
07732651205EACA300C4D1CD /* WAV-MP3.wav in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -365,47 +186,14 @@
|
||||
0707130B2067F2E000F789B3 /* QueueViewController.swift in Sources */,
|
||||
070713072067EB4F00F789B3 /* Double + Extensions.swift in Sources */,
|
||||
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
|
||||
0708ED722116E91D00EB29BD /* Source.swift in Sources */,
|
||||
0707130F2067F40A00F789B3 /* QueueTableViewCell.swift in Sources */,
|
||||
070713092067EFFB00F789B3 /* AudioController.swift in Sources */,
|
||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
607FACE11AFB9204008FA782 /* Sources */ = {
|
||||
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 */,
|
||||
0775575920668B020002C6A1 /* QueueManagerTests.swift in Sources */,
|
||||
074A6483205C155E0083D868 /* AVPlayerTimeObserverTests.swift in Sources */,
|
||||
078C908F210D263200555E80 /* AVPlayerItemObserverTests.swift in Sources */,
|
||||
9B521D0E2662937600EF0C3A /* MockDispatchQueue.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;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 607FACCF1AFB9204008FA782 /* SwiftAudio_Example */;
|
||||
targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
607FACD91AFB9204008FA782 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@@ -568,54 +356,6 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
607FACF31AFB9204008FA782 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 7U2TUNKNQX;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.doublesymmetry.--PRODUCT-NAME-rfc1034identifier-";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
607FACF41AFB9204008FA782 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
DEVELOPMENT_TEAM = 7U2TUNKNQX;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = Tests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.doublesymmetry.--PRODUCT-NAME-rfc1034identifier-";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -637,51 +377,9 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "SwiftAudio_Tests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
607FACF31AFB9204008FA782 /* Debug */,
|
||||
607FACF41AFB9204008FA782 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
9B05AA292660273200C7A389 /* XCRemoteSwiftPackageReference "Quick" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Quick/Quick";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 4.0.0;
|
||||
};
|
||||
};
|
||||
9B05AA2C2660274F00C7A389 /* XCRemoteSwiftPackageReference "Nimble" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Quick/Nimble";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 9.2.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
9B05AA302660276400C7A389 /* Quick */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9B05AA292660273200C7A389 /* XCRemoteSwiftPackageReference "Quick" */;
|
||||
productName = Quick;
|
||||
};
|
||||
9B05AA322660276400C7A389 /* Nimble */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9B05AA2C2660274F00C7A389 /* XCRemoteSwiftPackageReference "Nimble" */;
|
||||
productName = Nimble;
|
||||
};
|
||||
9B1D5E1D27C76F5C004CA883 /* SwiftAudioEx */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SwiftAudioEx;
|
||||
};
|
||||
9B1D5E1F27C76F6F004CA883 /* SwiftAudioEx */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = SwiftAudioEx;
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "CwlCatchException",
|
||||
"repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "682841464136f8c66e04afe5dbd01ab51a3a56f2",
|
||||
"version": "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "CwlPreconditionTesting",
|
||||
"repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "02b7a39a99c4da27abe03cab2053a9034379639f",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Nimble",
|
||||
"repositoryURL": "https://github.com/Quick/Nimble",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "af1730dde4e6c0d45bf01b99f8a41713ce536790",
|
||||
"version": "9.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Quick",
|
||||
"repositoryURL": "https://github.com/Quick/Quick",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "bd86ca0141e3cfb333546de5a11ede63f0c4a0e6",
|
||||
"version": "4.0.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
|
||||
class AVPlayerItemNotificationObserverTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("A notification observer") {
|
||||
|
||||
var item: AVPlayerItem!
|
||||
var observer: AVPlayerItemNotificationObserver!
|
||||
|
||||
beforeEach {
|
||||
item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
|
||||
observer = AVPlayerItemNotificationObserver()
|
||||
}
|
||||
|
||||
context("when started observing") {
|
||||
beforeEach {
|
||||
observer.startObserving(item: item)
|
||||
}
|
||||
|
||||
it("should have an observed item") {
|
||||
expect(observer.observingItem).toNot(beNil())
|
||||
}
|
||||
|
||||
context("when ended observing") {
|
||||
|
||||
beforeEach {
|
||||
observer.stopObservingCurrentItem()
|
||||
}
|
||||
|
||||
it("should have no observed item") {
|
||||
expect(observer.observingItem).to(beNil())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AVPlayerItemObserverTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("An AVPlayerItemObserver") {
|
||||
var observer: AVPlayerItemObserver!
|
||||
beforeEach {
|
||||
observer = AVPlayerItemObserver()
|
||||
}
|
||||
describe("observed item") {
|
||||
context("when observing") {
|
||||
var item: AVPlayerItem!
|
||||
beforeEach {
|
||||
item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
|
||||
observer.startObserving(item: item)
|
||||
}
|
||||
|
||||
it("should exist") {
|
||||
expect(observer.observingItem).toEventuallyNot(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("observing status") {
|
||||
it("should not be observing") {
|
||||
expect(observer.isObserving).toEventuallyNot(beTrue())
|
||||
}
|
||||
context("when observing") {
|
||||
var item: AVPlayerItem!
|
||||
beforeEach {
|
||||
item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
|
||||
observer.startObserving(item: item)
|
||||
}
|
||||
it("should be observing") {
|
||||
expect(observer.isObserving).toEventually(beTrue())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AVPlayerItemObserverDelegateHolder: AVPlayerItemObserverDelegate {
|
||||
func item(didUpdatePlaybackLikelyToKeepUp playbackLikelyToKeepUp: Bool) {
|
||||
|
||||
}
|
||||
|
||||
var receivedCommonMetadata: ((_ metadata: [AVMetadataItem]) -> Void)?
|
||||
|
||||
func item(didReceiveCommonMetadata metadata: [AVMetadataItem]) {
|
||||
receivedCommonMetadata?(metadata)
|
||||
}
|
||||
|
||||
|
||||
var receivedTimedMetadata: ((_ metadata: [AVTimedMetadataGroup]) -> Void)?
|
||||
|
||||
func item(didReceiveTimedMetadata metadata: [AVTimedMetadataGroup]) {
|
||||
receivedTimedMetadata?(metadata)
|
||||
}
|
||||
|
||||
|
||||
var receivedChapterMetadata: ((_ metadata: [AVTimedMetadataGroup]) -> Void)?
|
||||
|
||||
func item(didReceiveChapterMetadata metadata: [AVTimedMetadataGroup]) {
|
||||
receivedChapterMetadata?(metadata)
|
||||
}
|
||||
|
||||
|
||||
var updateDuration: ((_ duration: Double) -> Void)?
|
||||
|
||||
func item(didUpdateDuration duration: Double) {
|
||||
updateDuration?(duration)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
|
||||
class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
|
||||
|
||||
var status: AVPlayer.Status?
|
||||
var timeControlStatus: AVPlayer.TimeControlStatus?
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("A player observer") {
|
||||
|
||||
var player: AVPlayer!
|
||||
var observer: AVPlayerObserver!
|
||||
|
||||
beforeEach {
|
||||
player = AVPlayer()
|
||||
player.volume = 0.0
|
||||
observer = AVPlayerObserver()
|
||||
observer.player = player
|
||||
observer.delegate = self
|
||||
}
|
||||
|
||||
it("should not be observing") {
|
||||
expect(observer.isObserving).to(beFalse())
|
||||
}
|
||||
|
||||
context("when observing has started") {
|
||||
beforeEach {
|
||||
observer.startObserving()
|
||||
}
|
||||
|
||||
it("should be observing") {
|
||||
expect(observer.isObserving).toEventually(beTrue())
|
||||
}
|
||||
|
||||
context("when player has started") {
|
||||
beforeEach {
|
||||
player.replaceCurrentItem(with: AVPlayerItem(url: URL(fileURLWithPath: Source.path)))
|
||||
player.play()
|
||||
}
|
||||
|
||||
it("it should update the delegate") {
|
||||
expect(self.status).toEventuallyNot(beNil())
|
||||
expect(self.timeControlStatus).toEventuallyNot(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when observing again") {
|
||||
beforeEach {
|
||||
observer.startObserving()
|
||||
}
|
||||
|
||||
it("should be observing") {
|
||||
expect(observer.isObserving).toEventually(beTrue())
|
||||
}
|
||||
}
|
||||
|
||||
context("when stopping observing") {
|
||||
|
||||
beforeEach {
|
||||
observer.stopObserving()
|
||||
}
|
||||
|
||||
it("should not be observing") {
|
||||
expect(observer.isObserving).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func player(statusDidChange status: AVPlayer.Status) {
|
||||
self.status = status
|
||||
}
|
||||
|
||||
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus) {
|
||||
self.timeControlStatus = status
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AVPlayerTimeObserverTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("AVPlayerTimeObserver") {
|
||||
|
||||
var player: AVPlayer!
|
||||
var observer: AVPlayerTimeObserver!
|
||||
|
||||
beforeEach {
|
||||
player = AVPlayer()
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
player.volume = 0
|
||||
observer = AVPlayerTimeObserver(periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime())
|
||||
observer.player = player
|
||||
}
|
||||
|
||||
context("has started boundary time observing") {
|
||||
|
||||
beforeEach {
|
||||
observer.registerForBoundaryTimeEvents()
|
||||
}
|
||||
|
||||
it("should have a boundary token") {
|
||||
expect(observer.boundaryTimeStartObserverToken).toNot(beNil())
|
||||
}
|
||||
|
||||
context("has ended boundary time observing") {
|
||||
|
||||
beforeEach {
|
||||
observer.unregisterForBoundaryTimeEvents()
|
||||
}
|
||||
|
||||
it("should have no boundary token") {
|
||||
expect(observer.boundaryTimeStartObserverToken).to(beNil())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
context("has started periodic time observing") {
|
||||
|
||||
beforeEach {
|
||||
observer.registerForPeriodicTimeEvents()
|
||||
}
|
||||
|
||||
it("should have a periodic token") {
|
||||
expect(observer.periodicTimeObserverToken).toNot(beNil())
|
||||
}
|
||||
|
||||
context("ended periodic time observing") {
|
||||
|
||||
beforeEach {
|
||||
observer.unregisterForPeriodicEvents()
|
||||
}
|
||||
|
||||
it("should have no periodic token") {
|
||||
expect(observer.periodicTimeObserverToken).to(beNil())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import MediaPlayer
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
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") {
|
||||
expect(event.invokers.count).toEventually(equal(listeners.count - 1))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,590 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import Foundation
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AudioPlayerTests: QuickSpec {
|
||||
override func spec() {
|
||||
beforeSuite {
|
||||
Nimble.AsyncDefaults.timeout = .seconds(10)
|
||||
Nimble.AsyncDefaults.pollInterval = .milliseconds(100)
|
||||
}
|
||||
describe("AudioPlayer") {
|
||||
var audioPlayer: AudioPlayer!
|
||||
var listener: AudioPlayerEventListener!
|
||||
var playerStateEventListener: QueuedAudioPlayer.PlayerStateEventListener!
|
||||
beforeEach {
|
||||
audioPlayer = AudioPlayer()
|
||||
audioPlayer.volume = 0.0
|
||||
listener = AudioPlayerEventListener(audioPlayer: audioPlayer)
|
||||
playerStateEventListener = QueuedAudioPlayer.PlayerStateEventListener()
|
||||
audioPlayer.event.stateChange.addListener(
|
||||
playerStateEventListener,
|
||||
playerStateEventListener.handleEvent
|
||||
)
|
||||
}
|
||||
|
||||
afterEach {
|
||||
audioPlayer = nil
|
||||
listener = nil
|
||||
}
|
||||
|
||||
// MARK: - Load
|
||||
context("when loading audio item") {
|
||||
it("should never mutate playWhenReady to false") {
|
||||
audioPlayer.playWhenReady = true
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
expect(audioPlayer.playWhenReady).to(beTrue())
|
||||
}
|
||||
|
||||
it("should never mutate playWhenReady to true") {
|
||||
audioPlayer.playWhenReady = false
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
expect(audioPlayer.playWhenReady).to(beFalse())
|
||||
}
|
||||
|
||||
it("should mutate playWhenReady when loading with playWhenReady equals true") {
|
||||
audioPlayer.playWhenReady = true
|
||||
expect(audioPlayer.playWhenReady).to(beTrue())
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
expect(audioPlayer.playWhenReady).to(beFalse())
|
||||
}
|
||||
|
||||
it("should mutate playWhenReady when loading with playWhenReady equals false") {
|
||||
audioPlayer.playWhenReady = false
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
expect(audioPlayer.playWhenReady).to(beTrue())
|
||||
}
|
||||
|
||||
it("should seek when audio item sets initial time") {
|
||||
var seekCompleted = false
|
||||
listener.onSeekCompletion = {
|
||||
seekCompleted = true
|
||||
}
|
||||
audioPlayer.playWhenReady = false
|
||||
expect(audioPlayer.playWhenReady).to(beFalse())
|
||||
audioPlayer.load(item: FiveSecondSourceWithInitialTimeOfFourSeconds.getAudioItem())
|
||||
expect(seekCompleted).toEventually(beTrue())
|
||||
expect(audioPlayer?.currentTime ?? 0).to(beGreaterThanOrEqualTo(4))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Duration
|
||||
context("when dealing with duration") {
|
||||
it("should set duration eventually after loading") {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
expect(audioPlayer.duration).toEventually(beCloseTo(5, within: 0.1))
|
||||
}
|
||||
|
||||
it("audioPlayer.event.updateDuration should receive duration after loading") {
|
||||
var receivedUpdateDuration = false
|
||||
listener.onUpdateDuration = { duration in
|
||||
receivedUpdateDuration = true
|
||||
expect(duration).to(beCloseTo(5, within: 0.1))
|
||||
}
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
expect(receivedUpdateDuration).toEventually(beTrue())
|
||||
}
|
||||
|
||||
it("should reset duration after loading again") {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
expect(audioPlayer.duration).to(equal(0))
|
||||
expect(audioPlayer.duration).toEventually(beCloseTo(5, within: 0.1))
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
expect(audioPlayer.duration).to(equal(0))
|
||||
expect(audioPlayer.duration).toEventually(beCloseTo(5, within: 0.1))
|
||||
}
|
||||
|
||||
it("should reset duration after reset") {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
expect(audioPlayer.duration).to(equal(0))
|
||||
expect(audioPlayer.duration).toEventually(beCloseTo(5, within: 0.1))
|
||||
audioPlayer.clear()
|
||||
expect(audioPlayer.duration).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Failure
|
||||
context("when handling failure") {
|
||||
it("should emit fail event on load with non-malformed URL") {
|
||||
var didReceiveFail = false
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true
|
||||
}
|
||||
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: "", // malformed url
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
)
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
expect(audioPlayer.playbackError).toNot(beNil())
|
||||
expect(audioPlayer.playerState).to(equal(.failed))
|
||||
expect(didReceiveFail).to(beTrue())
|
||||
}
|
||||
|
||||
it("should emit fail event on load with non-existing resource") {
|
||||
var didReceiveFail = false
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true
|
||||
}
|
||||
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3"
|
||||
let item = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream)
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
expect(audioPlayer.playbackError).toEventuallyNot(beNil())
|
||||
expect(audioPlayer.playerState).to(equal(.failed))
|
||||
expect(didReceiveFail).to(beTrue())
|
||||
}
|
||||
|
||||
context("calling play after failure") {
|
||||
it("should retry loading") {
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3";
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: nonExistingUrl,
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
);
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal([.loading, .failed]))
|
||||
audioPlayer.play()
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal([.loading, .failed, .loading, .failed]))
|
||||
}
|
||||
}
|
||||
|
||||
context("setting playWhenReady after failure") {
|
||||
it("should retry loading") {
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3";
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: nonExistingUrl,
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
);
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal([.loading, .failed]))
|
||||
audioPlayer.playWhenReady = true
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal([ .loading, .failed, .loading, .failed]))
|
||||
}
|
||||
}
|
||||
|
||||
context("calling reload after failure") {
|
||||
it("should retry loading but fail again with same broken source") {
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3";
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: nonExistingUrl,
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
);
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal([.loading, .failed]))
|
||||
|
||||
audioPlayer.reload(startFromCurrentTime: true)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal([.loading, .failed, .loading, .failed]))
|
||||
}
|
||||
}
|
||||
|
||||
context("load resource") {
|
||||
it("should succeed after previous failure") {
|
||||
var didReceiveFail = false;
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true;
|
||||
}
|
||||
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3";
|
||||
let failItem = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream);
|
||||
audioPlayer.load(item: failItem, playWhenReady: false)
|
||||
expect(didReceiveFail).toEventually(beTrue())
|
||||
expect(audioPlayer.playerState).toEventually(equal(.failed))
|
||||
expect(playerStateEventListener.states).toEventually(equal([.loading, .failed]))
|
||||
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
expect(audioPlayer.playbackError).to(beNil())
|
||||
expect(playerStateEventListener.statesWithoutBuffering)
|
||||
.toEventually(equal([.loading, .failed, .loading, .playing]))
|
||||
}
|
||||
|
||||
it("with playWhenReady=false it should succeed after previous failure") {
|
||||
var didReceiveFail = false;
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true;
|
||||
}
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3";
|
||||
let item = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream);
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
expect(didReceiveFail).toEventually(beTrue())
|
||||
expect(audioPlayer.playerState).toEventually(equal(.failed))
|
||||
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
expect(audioPlayer.playbackError).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
// MARK: - States
|
||||
context("states") {
|
||||
it("should initially be idle") {
|
||||
expect(audioPlayer.playerState).to(equal(.idle))
|
||||
}
|
||||
|
||||
it("should be loading after load source") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
expect(audioPlayer.playerState).to(equal(.loading))
|
||||
}
|
||||
|
||||
it("should become ready after load source") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.ready))
|
||||
}
|
||||
|
||||
it("should be playing after load source with playWhenReady") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.playing))
|
||||
}
|
||||
it("should emit events in reliable order") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents : [AVPlayerWrapperState] = [.loading, .playing]
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
audioPlayer.pause()
|
||||
expectedEvents.append(.paused)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
expectedEvents.append(.playing)
|
||||
audioPlayer.play()
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
audioPlayer.clear()
|
||||
expectedEvents.append(.idle)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
}
|
||||
it("should update playWhenReady after external pause") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents : [AVPlayerWrapperState] = [.loading, .playing];
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
expect(audioPlayer.currentTime).toEventually(beGreaterThan(0.0))
|
||||
|
||||
// Simulate avplayer becoming paused due to external reason:
|
||||
audioPlayer.wrapper.rate = 0
|
||||
|
||||
expectedEvents.append(.paused);
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
expect(audioPlayer.playWhenReady).to(beFalse())
|
||||
}
|
||||
|
||||
it("should emit events in reliable order at end call stop") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents : [AVPlayerWrapperState] = [.loading, .playing]
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
|
||||
audioPlayer.pause()
|
||||
expectedEvents.append(.paused)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
|
||||
expectedEvents.append(.playing)
|
||||
audioPlayer.play()
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
|
||||
audioPlayer.stop()
|
||||
expectedEvents.append(.stopped)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
}
|
||||
|
||||
it("should emit events in reliable order also after loading after reset") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents : [AVPlayerWrapperState] = [.loading, .playing]
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
|
||||
audioPlayer.clear()
|
||||
expectedEvents.append(.idle)
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
expectedEvents.append(contentsOf: [.loading, .playing])
|
||||
expect(playerStateEventListener.statesWithoutBuffering).toEventually(equal(expectedEvents))
|
||||
}
|
||||
|
||||
it("should be playing after calling play()") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.ready))
|
||||
audioPlayer.play()
|
||||
expect(audioPlayer.playerState).toEventually(equal(.playing))
|
||||
}
|
||||
|
||||
it("should be paused after calling pause()") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.playing))
|
||||
audioPlayer.pause()
|
||||
expect(audioPlayer.playerState).toEventually(equal(.paused))
|
||||
}
|
||||
|
||||
it("should be paused after setting playWhenReady to false") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.playing))
|
||||
audioPlayer.playWhenReady = false
|
||||
expect(audioPlayer.playerState).toEventually(equal(.paused))
|
||||
}
|
||||
|
||||
it("should be playing after setting playWhenReady to true") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.ready))
|
||||
audioPlayer.playWhenReady = true
|
||||
expect(audioPlayer.playerState).toEventually(equal(.playing))
|
||||
}
|
||||
|
||||
it("should be stopped after stop") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.playing))
|
||||
audioPlayer.stop()
|
||||
expect(audioPlayer.playerState).toEventually(equal(.stopped))
|
||||
}
|
||||
}
|
||||
// MARK: - States
|
||||
context("current time") {
|
||||
it("should be 0 initially") {
|
||||
expect(audioPlayer.currentTime).to(equal(0.0))
|
||||
}
|
||||
|
||||
it("audioPlayer.event.secondElapse should be emitted when playing") {
|
||||
var onSecondsElapseTime = 0.0
|
||||
audioPlayer.timeEventFrequency = .everyQuarterSecond
|
||||
listener.onSecondsElapse = { time in
|
||||
onSecondsElapseTime = time
|
||||
}
|
||||
audioPlayer.load(item: LongSource.getAudioItem(), playWhenReady: true)
|
||||
expect(onSecondsElapseTime).toEventually(beGreaterThan(0))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Buffer
|
||||
context("buffer") {
|
||||
it("automaticallyWaitsToMinimizeStalling should be true") {
|
||||
expect(audioPlayer.automaticallyWaitsToMinimizeStalling).to(beTrue())
|
||||
}
|
||||
it("bufferDuration should be zero") {
|
||||
expect(audioPlayer.bufferDuration).to(equal(0))
|
||||
}
|
||||
it("setting bufferDuration disables automaticallyWaitsToMinimizeStalling") {
|
||||
audioPlayer.bufferDuration = 1;
|
||||
expect(audioPlayer.bufferDuration).to(equal(1))
|
||||
expect(audioPlayer.automaticallyWaitsToMinimizeStalling).to(beFalse())
|
||||
}
|
||||
it("enabling automaticallyWaitsToMinimizeStalling sets bufferDuration to zero") {
|
||||
audioPlayer.automaticallyWaitsToMinimizeStalling = true
|
||||
expect(audioPlayer.bufferDuration).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Seek
|
||||
context("Seek") {
|
||||
it("Seeking should work before loading is complete") {
|
||||
let player = audioPlayer
|
||||
player!.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
player!.seek(to: 4.75)
|
||||
expect(audioPlayer.currentTime).toEventually(beGreaterThan(4.75))
|
||||
}
|
||||
it("Seeking should work after loading is complete") {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
expect(audioPlayer.currentTime).toEventually(beGreaterThan(4.75))
|
||||
}
|
||||
it("Seeking should work when paused") {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: false)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
expect(audioPlayer.currentTime).toEventually(equal(4.75))
|
||||
}
|
||||
it("Seeking can not change currentTime when stopped") {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: false)
|
||||
audioPlayer.stop()
|
||||
audioPlayer.seek(to: 4.75)
|
||||
expect(audioPlayer.currentTime).toNotEventually(equal(4.75))
|
||||
expect(audioPlayer.currentTime).to(equal(0))
|
||||
}
|
||||
}
|
||||
// MARK: - Rate
|
||||
context("Rate") {
|
||||
it("should be 1 initially") {
|
||||
expect(audioPlayer.rate).to(equal(1))
|
||||
}
|
||||
it("should speed up playback when setting to more than 1") {
|
||||
var start: Date? = nil;
|
||||
var end: Date? = nil;
|
||||
|
||||
listener.onPlaybackEnd = { reason in
|
||||
if (reason == .playedUntilEnd) {
|
||||
end = Date()
|
||||
}
|
||||
}
|
||||
|
||||
listener.onStateChange = { state in
|
||||
switch state {
|
||||
case .playing:
|
||||
if (start == nil) {
|
||||
start = Date()
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
audioPlayer.rate = 10
|
||||
expect(audioPlayer.playerState).toEventually(equal(.ended))
|
||||
if let start = start, let end = end {
|
||||
let duration = end.timeIntervalSince(start);
|
||||
expect(duration).to(beLessThan(1))
|
||||
}
|
||||
}
|
||||
|
||||
it("should slow down playback when setting to less than 1") {
|
||||
var start: Date? = nil;
|
||||
var end: Date? = nil;
|
||||
|
||||
listener.onPlaybackEnd = { reason in
|
||||
if (reason == .playedUntilEnd) {
|
||||
end = Date()
|
||||
}
|
||||
}
|
||||
|
||||
audioPlayer.rate = 0.5
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
listener.onStateChange = { state in
|
||||
switch state {
|
||||
case .playing:
|
||||
if (start == nil) {
|
||||
start = Date()
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
audioPlayer.seek(to: 4.75)
|
||||
expect(audioPlayer.playerState).toEventually(equal(.ended))
|
||||
if let start = start, let end = end {
|
||||
let duration = end.timeIntervalSince(start);
|
||||
expect(duration).to(beLessThanOrEqualTo(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
// MARK: - Current Item
|
||||
context("Current Item") {
|
||||
it("should be nil initially") {
|
||||
expect(audioPlayer.currentItem).to(beNil())
|
||||
}
|
||||
it("should not be nil after loading") {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
expect(audioPlayer.currentItem?.getSourceUrl()).to(equal(Source.getAudioItem().getSourceUrl()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerStateEventListener {
|
||||
private let lockQueue = DispatchQueue(
|
||||
label: "PlayerStateEventListener.lockQueue",
|
||||
target: .global()
|
||||
)
|
||||
var _states: [AudioPlayerState] = []
|
||||
var states: [AudioPlayerState] {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _states
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
lockQueue.sync {
|
||||
_states = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
private var _statesWithoutBuffering: [AudioPlayerState] = []
|
||||
var statesWithoutBuffering: [AudioPlayerState] {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _statesWithoutBuffering
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
lockQueue.sync {
|
||||
_statesWithoutBuffering = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
func handleEvent(state: AudioPlayerState) {
|
||||
states.append(state)
|
||||
if (state != .ready && state != .buffering && (statesWithoutBuffering.isEmpty || statesWithoutBuffering.last != state)) {
|
||||
statesWithoutBuffering.append(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AudioPlayerEventListener {
|
||||
|
||||
var state: AudioPlayerState?
|
||||
|
||||
var onStateChange: ((_ state: AudioPlayerState) -> Void)?
|
||||
var onSecondsElapse: ((_ seconds: TimeInterval) -> Void)?
|
||||
var onSeekCompletion: (() -> Void)?
|
||||
var onReceiveFail: ((_ error: Error?) -> Void)?
|
||||
var onPlaybackEnd: ((_: AudioPlayer.PlaybackEndEventData) -> Void)?
|
||||
var onUpdateDuration: ((_: AudioPlayer.UpdateDurationEventData) -> Void)?
|
||||
|
||||
weak var audioPlayer: AudioPlayer?
|
||||
|
||||
init(audioPlayer: AudioPlayer) {
|
||||
audioPlayer.event.updateDuration.addListener(self, handleUpdateDuration)
|
||||
audioPlayer.event.stateChange.addListener(self, handleStateChange)
|
||||
audioPlayer.event.seek.addListener(self, handleSeek)
|
||||
audioPlayer.event.secondElapse.addListener(self, handleSecondsElapse)
|
||||
audioPlayer.event.fail.addListener(self, handleFail)
|
||||
audioPlayer.event.playbackEnd.addListener(self, handlePlaybackEnd)
|
||||
}
|
||||
|
||||
deinit {
|
||||
audioPlayer?.event.stateChange.removeListener(self)
|
||||
audioPlayer?.event.seek.removeListener(self)
|
||||
audioPlayer?.event.secondElapse.removeListener(self)
|
||||
}
|
||||
|
||||
func handleStateChange(state: AudioPlayerState) {
|
||||
self.state = state
|
||||
onStateChange?(state)
|
||||
}
|
||||
|
||||
func handleSeek(data: AudioPlayer.SeekEventData) {
|
||||
onSeekCompletion?()
|
||||
}
|
||||
|
||||
func handleSecondsElapse(data: AudioPlayer.SecondElapseEventData) {
|
||||
self.onSecondsElapse?(data)
|
||||
}
|
||||
|
||||
func handleFail(error: Error?) {
|
||||
self.onReceiveFail?(error)
|
||||
}
|
||||
|
||||
func handlePlaybackEnd(_ data: AudioPlayer.PlaybackEndEventData) {
|
||||
self.onPlaybackEnd?(data)
|
||||
}
|
||||
|
||||
func handleUpdateDuration(_ data: AudioPlayer.UpdateDurationEventData) {
|
||||
self.onUpdateDuration?(data)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
static func random(length: Int = 20) -> String {
|
||||
let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
var randomString: String = ""
|
||||
|
||||
for _ in 0..<length {
|
||||
let randomValue = arc4random_uniform(UInt32(base.count))
|
||||
randomString += "\(base[base.index(base.startIndex, offsetBy: Int(randomValue))])"
|
||||
}
|
||||
return randomString
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import AVFoundation
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AudioSessionControllerTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("An AudioSessionController") {
|
||||
let audioSessionController: AudioSessionController = AudioSessionController(audioSession: NonFailingAudioSession())
|
||||
|
||||
it("should be inactive") {
|
||||
expect(audioSessionController.audioSessionIsActive).to(beFalse())
|
||||
}
|
||||
|
||||
context("when session is activated") {
|
||||
beforeEach {
|
||||
try? audioSessionController.activateSession()
|
||||
}
|
||||
|
||||
it("should be active") {
|
||||
expect(audioSessionController.audioSessionIsActive).to(beTrue())
|
||||
}
|
||||
|
||||
context("when deactivating session") {
|
||||
beforeEach {
|
||||
try? audioSessionController.deactivateSession()
|
||||
}
|
||||
|
||||
it("should be inactive") {
|
||||
expect(audioSessionController.audioSessionIsActive).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("its isObservingForInterruptions") {
|
||||
it("should be true") {
|
||||
expect(audioSessionController.isObservingForInterruptions).to(beTrue())
|
||||
}
|
||||
|
||||
context("when isObservingForInterruptions is set to false") {
|
||||
beforeEach {
|
||||
audioSessionController.isObservingForInterruptions = false
|
||||
}
|
||||
|
||||
it("should be false") {
|
||||
expect(audioSessionController.isObservingForInterruptions).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("its delegate") {
|
||||
context("when a ended interruption arrives") {
|
||||
var delegate: AudioSessionControllerDelegateImplementation!
|
||||
beforeEach {
|
||||
let notification = Notification(name: AVAudioSession.interruptionNotification, object: nil, userInfo: [
|
||||
AVAudioSessionInterruptionTypeKey: UInt(0),
|
||||
AVAudioSessionInterruptionOptionKey: UInt(1),
|
||||
])
|
||||
delegate = AudioSessionControllerDelegateImplementation()
|
||||
audioSessionController.delegate = delegate
|
||||
audioSessionController.handleInterruption(notification: notification)
|
||||
}
|
||||
|
||||
it("should eventually be updated with the interruption type") {
|
||||
expect(delegate.interruptionType).toEventually(equal(InterruptionType.ended(shouldResume: true)))
|
||||
}
|
||||
|
||||
}
|
||||
context("when a begin interruption arrives") {
|
||||
var delegate: AudioSessionControllerDelegateImplementation!
|
||||
beforeEach {
|
||||
let notification = Notification(name: AVAudioSession.interruptionNotification, object: nil, userInfo: [
|
||||
AVAudioSessionInterruptionTypeKey: UInt(1),
|
||||
])
|
||||
delegate = AudioSessionControllerDelegateImplementation()
|
||||
audioSessionController.delegate = delegate
|
||||
audioSessionController.handleInterruption(notification: notification)
|
||||
}
|
||||
|
||||
it("should eventually be updated with the interruption type") {
|
||||
expect(delegate.interruptionType).toEventually(equal(InterruptionType.began))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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") {
|
||||
expect(audioSessionController.audioSessionIsActive).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelegate {
|
||||
var interruptionType: InterruptionType? = nil
|
||||
|
||||
func handleInterruption(type: InterruptionType) {
|
||||
self.interruptionType = type
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?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>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,72 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import MediaPlayer
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class NowPlayingInfoControllerTests: QuickSpec {
|
||||
|
||||
override func spec() {
|
||||
describe("An NowPlayingInfoController") {
|
||||
|
||||
var nowPlayingController: NowPlayingInfoController!
|
||||
|
||||
beforeEach {
|
||||
nowPlayingController = NowPlayingInfoController(dispatchQueue: MockDispatchQueue(), 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") {
|
||||
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") {
|
||||
expect(nowPlayingController.infoCenter.nowPlayingInfo?.count).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
import MediaPlayer
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
/// 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()
|
||||
audioPlayer.load(item: item, playWhenReady: false)
|
||||
}
|
||||
|
||||
it("should eventually be updated with meta data") {
|
||||
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()
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
}
|
||||
|
||||
it("should eventually be updated with playback values") {
|
||||
expect(nowPlayingController.getRate()).toEventuallyNot(beNil())
|
||||
expect(nowPlayingController.getDuration()).toEventuallyNot(beNil())
|
||||
expect(nowPlayingController.getCurrentTime()).toEventuallyNot(beNil())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,673 +0,0 @@
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
|
||||
class QueueManagerTests: QuickSpec {
|
||||
|
||||
let dummyItem = 0
|
||||
|
||||
let items: [Int] = [0, 1, 2]
|
||||
|
||||
override func spec() {
|
||||
|
||||
describe("A QueueManager") {
|
||||
|
||||
var queue: QueueManager<Int>!
|
||||
|
||||
beforeEach {
|
||||
queue = QueueManager()
|
||||
}
|
||||
|
||||
describe("its current item") {
|
||||
|
||||
it("should be nil starting out") {
|
||||
expect(queue.current).to(beNil())
|
||||
}
|
||||
|
||||
context("when one item is added") {
|
||||
beforeEach {
|
||||
queue.add(self.dummyItem)
|
||||
}
|
||||
|
||||
it("should be nil, because it wasn't jumped to") {
|
||||
expect(queue.current).to(beNil())
|
||||
}
|
||||
|
||||
context("after being jumped to") {
|
||||
beforeEach {
|
||||
try! queue.jump(to: 0)
|
||||
}
|
||||
|
||||
it("should be the added item") {
|
||||
expect(queue.current).to(equal(self.dummyItem))
|
||||
}
|
||||
|
||||
context("then replaced") {
|
||||
beforeEach {
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
}
|
||||
it("should be the new item") {
|
||||
expect(queue.current).to(equal(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("when replacing the current item when the queue is still empty") {
|
||||
beforeEach {
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
}
|
||||
|
||||
it("the current item should be the replaced item") {
|
||||
expect(queue.current).toNot(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when multiple items are added and the last is jumped to") {
|
||||
beforeEach {
|
||||
queue.add(self.items)
|
||||
try! queue.jump(to: queue.items.count - 1)
|
||||
}
|
||||
|
||||
it("should not be nil") {
|
||||
expect(queue.current).toNot(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
describe("when adding at index") {
|
||||
context("adding item at index 0 when queue is empty") {
|
||||
beforeEach {
|
||||
try! queue.add([3], at: 0)
|
||||
}
|
||||
it("should add element successfully") {
|
||||
expect(queue.items.first).to(equal(3))
|
||||
}
|
||||
it("should not set currentItem") {
|
||||
expect(queue.current).to(beNil())
|
||||
}
|
||||
it("should not set currentIndex") {
|
||||
expect(queue.currentIndex).to(equal(-1))
|
||||
}
|
||||
}
|
||||
|
||||
context("adding item at index and jumping to the first item") {
|
||||
beforeEach {
|
||||
queue.add([1, 2])
|
||||
try! queue.jump(to: 0)
|
||||
}
|
||||
|
||||
context("adding item at current [element count]") {
|
||||
it("should add element successfully") {
|
||||
try queue.add([3, 4, 5], at: queue.items.count)
|
||||
expect(queue.items.last).to(equal(5))
|
||||
}
|
||||
|
||||
context("before the first item") {
|
||||
it("should add element successfully") {
|
||||
try queue.add([-1], at: 0)
|
||||
expect(queue.items.first).to(equal(-1))
|
||||
}
|
||||
}
|
||||
|
||||
context("after the last item") {
|
||||
it("should add element successfully") {
|
||||
try queue.add([6], at: queue.items.count)
|
||||
expect(queue.items.last).to(equal(6))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("calling next, causing currentIndex to become 1, then adding at index 1") {
|
||||
beforeEach {
|
||||
queue.next()
|
||||
try! queue.add([5], at: queue.currentIndex)
|
||||
}
|
||||
it("should cause the current item to be shifted to index 2") {
|
||||
expect(queue.current).to(equal(2))
|
||||
expect(queue.currentIndex).to(equal(2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("when adding one item but not jumping to it yet") {
|
||||
|
||||
beforeEach {
|
||||
queue.add(0)
|
||||
}
|
||||
|
||||
it("should have an item in the queue") {
|
||||
expect(queue.items.count).to(equal(1))
|
||||
}
|
||||
|
||||
context("then replacing the item") {
|
||||
beforeEach {
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
}
|
||||
it("should have added an item and jumped to it") {
|
||||
expect(queue.items.count).to(equal(2))
|
||||
expect(queue.current).to(equal(1))
|
||||
expect(queue.currentIndex).to(equal(1))
|
||||
}
|
||||
|
||||
context("then calling next") {
|
||||
var item: Int?
|
||||
beforeEach {
|
||||
item = queue.next()
|
||||
}
|
||||
|
||||
it("should noop") {
|
||||
expect(item).to(equal(1))
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling previous") {
|
||||
var item: Int?
|
||||
beforeEach {
|
||||
item = queue.previous()
|
||||
}
|
||||
|
||||
it("should go back to the first") {
|
||||
expect(item).to(equal(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next") {
|
||||
var item: Int?
|
||||
beforeEach {
|
||||
item = queue.next()
|
||||
}
|
||||
|
||||
it("should noop") {
|
||||
expect(item).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling previous") {
|
||||
var item: Int?
|
||||
beforeEach {
|
||||
item = queue.previous()
|
||||
}
|
||||
|
||||
it("should noop") {
|
||||
expect(item).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("then jumping to 0 and calling next(wrap: true)") {
|
||||
|
||||
var nextIndex: Int?
|
||||
|
||||
beforeEach {
|
||||
try! queue.jump(to: 0)
|
||||
nextIndex = queue.next(wrap: true)
|
||||
}
|
||||
|
||||
it("should wrap to itself") {
|
||||
expect(nextIndex).to(equal(0))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
context("then jumping to 0 and then calling previous(wrap: true") {
|
||||
var previousIndex: Int?
|
||||
|
||||
beforeEach {
|
||||
try! queue.jump(to: 0)
|
||||
previousIndex = queue.previous(wrap: true)
|
||||
}
|
||||
|
||||
it("should wrap to itself") {
|
||||
expect(previousIndex).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
context("when adding multiple items") {
|
||||
|
||||
beforeEach {
|
||||
queue.add(self.items)
|
||||
}
|
||||
|
||||
it("should have items in the queue") {
|
||||
expect(queue.items.count).to(equal(self.items.count))
|
||||
}
|
||||
|
||||
it("the current item should be nil") {
|
||||
expect(queue.current).to(beNil())
|
||||
}
|
||||
|
||||
it("should not have next items") {
|
||||
expect(queue.nextItems.count).to(equal(0))
|
||||
}
|
||||
|
||||
context("when jumping to first item") {
|
||||
beforeEach {
|
||||
try! queue.jump(to: 0)
|
||||
}
|
||||
|
||||
context("then calling next") {
|
||||
var nextItem: Int?
|
||||
beforeEach {
|
||||
nextItem = queue.next()
|
||||
}
|
||||
|
||||
it("should return the next item") {
|
||||
expect(nextItem).toNot(beNil())
|
||||
expect(nextItem).to(equal(self.items[1]))
|
||||
}
|
||||
|
||||
it("should have next current item") {
|
||||
expect(queue.current).to(equal(self.items[1]))
|
||||
}
|
||||
|
||||
it("should have previous items") {
|
||||
expect(queue.previousItems).toNot(beNil())
|
||||
}
|
||||
|
||||
context("then calling previous") {
|
||||
var index: Int?
|
||||
beforeEach {
|
||||
index = queue.previous()
|
||||
}
|
||||
it("should return the first item") {
|
||||
expect(index).to(equal(0))
|
||||
}
|
||||
it("should have the previous current item") {
|
||||
expect(queue.current).to(equal(self.items.first))
|
||||
}
|
||||
context("then calling previous at the start of the queue") {
|
||||
var index: Int?
|
||||
beforeEach {
|
||||
index = queue.previous()
|
||||
}
|
||||
it("should noop and return the first item") {
|
||||
expect(index).to(equal(0))
|
||||
}
|
||||
}
|
||||
context("then calling previous(wrap: true)") {
|
||||
var index: Int?
|
||||
beforeEach {
|
||||
index = queue.previous(wrap: true)
|
||||
}
|
||||
it("should return the last item") {
|
||||
expect(index).to(equal(queue.items.count - 1))
|
||||
expect(queue.currentIndex).to(equal(queue.items.count - 1))
|
||||
expect(queue.current).to(equal(self.items.last))
|
||||
}
|
||||
|
||||
context("then calling next again at the end of the queue") {
|
||||
var index: Int?
|
||||
beforeEach {
|
||||
index = queue.next()
|
||||
}
|
||||
it("should noop and return the last item") {
|
||||
expect(index).to(equal(self.items.count - 1))
|
||||
}
|
||||
}
|
||||
|
||||
context("then calling next(wrap: true)") {
|
||||
var index: Int?
|
||||
beforeEach {
|
||||
index = queue.next(wrap: true)
|
||||
}
|
||||
it("should return the first item") {
|
||||
expect(index).to(equal(0))
|
||||
expect(queue.currentIndex).to(equal(0))
|
||||
expect(queue.current).to(equal(self.items.first))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing previous items") {
|
||||
beforeEach {
|
||||
queue.removePreviousItems()
|
||||
}
|
||||
it("should have no previous items") {
|
||||
expect(queue.previousItems.count).to(equal(0))
|
||||
}
|
||||
it("should have current index zero") {
|
||||
expect(queue.currentIndex).to(equal(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("adding more items") {
|
||||
var initialItemCount: Int!
|
||||
let newItems: [Int] = [10, 11, 12, 13]
|
||||
beforeEach {
|
||||
initialItemCount = queue.items.count
|
||||
try? queue.add(newItems, at: queue.items.endIndex - 1)
|
||||
}
|
||||
|
||||
it("should have more items") {
|
||||
expect(queue.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 = queue.currentIndex
|
||||
try? queue.add(newItems, at: initialCurrentIndex)
|
||||
}
|
||||
|
||||
it("currentIndex should increase by number of new items") {
|
||||
expect(queue.currentIndex).to(equal(initialCurrentIndex + newItems.count))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Removal
|
||||
|
||||
context("then removing a item with index less than currentIndex") {
|
||||
var removed: Int?
|
||||
var initialCurrentIndex: Int!
|
||||
beforeEach {
|
||||
let _ = try? queue.jump(to: 1)
|
||||
initialCurrentIndex = queue.currentIndex
|
||||
removed = try? queue.removeItem(at: initialCurrentIndex - 1)
|
||||
}
|
||||
|
||||
it("should remove the first item") {
|
||||
expect(removed).to(equal(0))
|
||||
}
|
||||
|
||||
it("should have set the initial current index to 1") {
|
||||
expect(initialCurrentIndex).to(equal(1))
|
||||
}
|
||||
|
||||
it("should decremented the currentIndex from 1 to 0") {
|
||||
expect(queue.currentIndex).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing the second item") {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? queue.removeItem(at: 1)
|
||||
}
|
||||
|
||||
it("should have one less item") {
|
||||
expect(removed).toNot(beNil())
|
||||
expect(queue.items.count).to(equal(self.items.count - 1))
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing the last item") {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? queue.removeItem(at: self.items.count - 1)
|
||||
}
|
||||
|
||||
it("should have one less item") {
|
||||
expect(removed).toNot(beNil())
|
||||
expect(queue.items.count).to(equal(self.items.count - 1))
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing the current item when it is the first item") {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? queue.removeItem(at: queue.currentIndex)
|
||||
}
|
||||
it("should remove the current item, and make the next item current") {
|
||||
expect(removed).toNot(beNil())
|
||||
expect(queue.items.count).to(equal(self.items.count - 1))
|
||||
expect(queue.currentIndex).to(equal(0))
|
||||
expect(queue.current).to(equal(1))
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing the current item when it is the last item") {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
try! queue.jump(to: queue.items.count - 1);
|
||||
removed = try? queue.removeItem(at: queue.currentIndex)
|
||||
}
|
||||
it("should remove the current item") {
|
||||
expect(removed).toNot(beNil())
|
||||
expect(queue.items.count).to(equal(self.items.count - 1))
|
||||
expect(queue.currentIndex).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing with too large index") {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? queue.removeItem(at: self.items.count)
|
||||
}
|
||||
|
||||
it("should not remove any items") {
|
||||
expect(removed).to(beNil())
|
||||
expect(queue.items.count).to(equal(self.items.count))
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing with too small index") {
|
||||
var removed: Int?
|
||||
beforeEach {
|
||||
removed = try? queue.removeItem(at: -1)
|
||||
}
|
||||
|
||||
it("should not remove any items") {
|
||||
expect(removed).to(beNil())
|
||||
expect(queue.items.count).to(equal(self.items.count))
|
||||
}
|
||||
}
|
||||
|
||||
context("then removing upcoming items") {
|
||||
beforeEach {
|
||||
queue.removeUpcomingItems()
|
||||
}
|
||||
|
||||
it("should have no next items") {
|
||||
expect(queue.nextItems.count).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Jumping
|
||||
|
||||
context("then jumping to the current item") {
|
||||
var error: Error?
|
||||
var item: Int?
|
||||
beforeEach {
|
||||
do {
|
||||
item = try queue.jump(to: queue.currentIndex)
|
||||
}
|
||||
catch let err {
|
||||
error = err
|
||||
}
|
||||
}
|
||||
|
||||
it("should return an item") {
|
||||
expect(item).toNot(beNil())
|
||||
}
|
||||
|
||||
it("should not throw an error") {
|
||||
expect(error).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("then jumping to the second item") {
|
||||
var jumped: Int?
|
||||
beforeEach {
|
||||
try? jumped = queue.jump(to: 1)
|
||||
}
|
||||
|
||||
it("should return the current item") {
|
||||
expect(jumped).toNot(beNil())
|
||||
expect(jumped).to(equal(queue.current))
|
||||
}
|
||||
|
||||
it("should move the current index") {
|
||||
expect(queue.currentIndex).to(equal(1))
|
||||
}
|
||||
}
|
||||
|
||||
context("then jumping to last item") {
|
||||
var jumped: Int?
|
||||
beforeEach {
|
||||
try? jumped = queue.jump(to: queue.items.count - 1)
|
||||
}
|
||||
it("should return the current item") {
|
||||
expect(jumped).toNot(beNil())
|
||||
expect(jumped).to(equal(queue.current))
|
||||
}
|
||||
|
||||
it("should move the current index") {
|
||||
expect(queue.currentIndex).to(equal(queue.items.count - 1))
|
||||
}
|
||||
}
|
||||
|
||||
context("then jumping to a negative index") {
|
||||
var jumped: Int?
|
||||
beforeEach {
|
||||
jumped = try? queue.jump(to: -1)
|
||||
}
|
||||
|
||||
it("should not return") {
|
||||
expect(jumped).to(beNil())
|
||||
}
|
||||
|
||||
it("should not move the current index") {
|
||||
expect(queue.currentIndex).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
context("then jumping with too large index") {
|
||||
var jumped: Int?
|
||||
beforeEach {
|
||||
jumped = try? queue.jump(to: queue.items.count)
|
||||
}
|
||||
it("should not return") {
|
||||
expect(jumped).to(beNil())
|
||||
}
|
||||
|
||||
it("should not move the current index") {
|
||||
expect(queue.currentIndex).to(equal(0))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Moving
|
||||
|
||||
context("moving the current item up one") {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try queue.moveItem(fromIndex: queue.currentIndex, toIndex: queue.currentIndex + 1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should not throw an error") {
|
||||
expect(error).to(beNil())
|
||||
}
|
||||
it("should change currentIndex") {
|
||||
expect(queue.currentIndex).to(equal(1))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
context("moving from a negative index") {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try queue.moveItem(fromIndex: -1, toIndex: queue.currentIndex + 1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should throw an error") {
|
||||
expect(error).toNot(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("moving from a too large index") {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try queue.moveItem(fromIndex: queue.items.count, toIndex: queue.currentIndex + 1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should throw an error") {
|
||||
expect(error).toNot(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("moving to a negative index") {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try queue.moveItem(fromIndex: queue.currentIndex + 1, toIndex: -1)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should throw an error") {
|
||||
expect(error).toNot(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("moving to a too large index") {
|
||||
var error: Error?
|
||||
beforeEach {
|
||||
do {
|
||||
try queue.moveItem(fromIndex: 0, toIndex: queue.items.count)
|
||||
}
|
||||
catch let err { error = err }
|
||||
}
|
||||
|
||||
it("should not throw an error") {
|
||||
expect(error).to(beNil())
|
||||
}
|
||||
|
||||
it("should have moved the first item to the end of the queue") {
|
||||
expect(queue.items.last).to(equal(0))
|
||||
}
|
||||
it("the first item in the queue should be what was previously the second item") {
|
||||
expect(queue.items.first).to(equal(1))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
context("then moving 2nd to 3rd") {
|
||||
let afterMoving: [Int] = [0, 2, 1]
|
||||
beforeEach {
|
||||
try? queue.moveItem(fromIndex: 1, toIndex: 3)
|
||||
}
|
||||
|
||||
it("should move the item") {
|
||||
expect(queue.items).to(equal(afterMoving))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Clear
|
||||
|
||||
context("when queue is cleared") {
|
||||
beforeEach {
|
||||
queue.clearQueue()
|
||||
}
|
||||
|
||||
it("should have currentIndex -1") {
|
||||
expect(queue.currentIndex).to(equal(-1))
|
||||
}
|
||||
|
||||
it("should have no items") {
|
||||
expect(queue.items.count).to(equal(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+8
-2
@@ -13,7 +13,13 @@ let package = Package(
|
||||
targets: [
|
||||
.target(
|
||||
name: "SwiftAudioEx",
|
||||
dependencies: [],
|
||||
path: "SwiftAudioEx/Classes")
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "SwiftAudioExTests",
|
||||
dependencies: ["SwiftAudioEx"],
|
||||
resources: [
|
||||
.process("Resources")
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||

|
||||
|
||||
# SwiftAudio
|
||||
# SwiftAudioEx
|
||||
|
||||
[](https://codecov.io/gh/DoubleSymmetry/SwiftAudio)
|
||||
[](http://cocoapods.org/pods/SwiftAudio)
|
||||
[](http://cocoapods.org/pods/SwiftAudio)
|
||||
[](http://cocoapods.org/pods/SwiftAudioEx)
|
||||
[](http://cocoapods.org/pods/SwiftAudioEx)
|
||||
|
||||
SwiftAudio is an audio player written in Swift, making it simpler to work with audio playback from streams and files.
|
||||
SwiftAudioEx is an audio player written in Swift, making it simpler to work with audio playback from streams and files.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -24,34 +23,34 @@ iOS 11.0+
|
||||
### Swift Package Manager
|
||||
[Swift Package Manager](https://swift.org/package-manager/) (SwiftPM) is a tool for managing the distribution of Swift code as well as C-family dependency. From Xcode 11, SwiftPM got natively integrated with Xcode.
|
||||
|
||||
SwiftAudio supports SwiftPM from version 0.12.0. To use SwiftPM, you should use Xcode 11 to open your project. Click `File` -> `Swift Packages` -> `Add Package Dependency`, enter [SwiftAudio repo's URL](https://github.com/DoubleSymmetry/SwiftAudio.git). Or you can login Xcode with your GitHub account and just type `SwiftAudio` to search.
|
||||
SwiftAudioEx supports SwiftPM from version 0.12.0. To use SwiftPM, you should use Xcode 11 to open your project. Click `File` -> `Swift Packages` -> `Add Package Dependency`, enter [SwiftAudioEx repo's URL](https://github.com/doublesymmetry/SwiftAudio.git). Or you can login Xcode with your GitHub account and just type `SwiftAudioEx` to search.
|
||||
|
||||
After select the package, you can choose the dependency type (tagged version, branch or commit). Then Xcode will setup all the stuff for you.
|
||||
|
||||
If you're a framework author and use SwiftAudio as a dependency, update your `Package.swift` file:
|
||||
If you're a framework author and use SwiftAudioEx as a dependency, update your `Package.swift` file:
|
||||
|
||||
```swift
|
||||
let package = Package(
|
||||
// 0.12.0 ..< 1.0.0
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/DoubleSymmetry/SwiftAudio.git", from: "0.12.0")
|
||||
.package(url: "https://github.com/doublesymmetry/SwiftAudio.git", from: "1.0.0")
|
||||
],
|
||||
// ...
|
||||
)
|
||||
```
|
||||
|
||||
### CocoaPods
|
||||
SwiftAudio is available through [CocoaPods](http://cocoapods.org). To install
|
||||
SwiftAudioEx is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod 'SwiftAudioEx', '~> 0.15.3'
|
||||
pod 'SwiftAudioEx', '~> 1.0.0'
|
||||
```
|
||||
|
||||
### Carthage
|
||||
SwiftAudio supports [Carthage](https://github.com/Carthage/Carthage). Add this to your Cartfile:
|
||||
SwiftAudioEx supports [Carthage](https://github.com/Carthage/Carthage). Add this to your Cartfile:
|
||||
```ruby
|
||||
github "jorgenhenrichsen/SwiftAudio" ~> 0.11.2
|
||||
github "doublesymmetry/SwiftAudioEx" ~> 1.0.0
|
||||
```
|
||||
Then follow the rest of Carthage instructions on [adding a framework](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application).
|
||||
|
||||
@@ -187,8 +186,8 @@ Make your `AudioItem`-subclass conform to `InitialTiming` to be able to start pl
|
||||
|
||||
## Author
|
||||
|
||||
Jørgen Henrichsen
|
||||
Originally: Jørgen Henrichsen now extended by David Chavez and other contributors.
|
||||
|
||||
## License
|
||||
|
||||
SwiftAudio is available under the MIT license. See the LICENSE file for more info.
|
||||
SwiftAudioEx is available under the MIT license. See the LICENSE file for more info.
|
||||
|
||||
@@ -137,7 +137,12 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
|
||||
|
||||
public var rate: Float {
|
||||
get { wrapper.rate }
|
||||
set { wrapper.rate = newValue }
|
||||
set {
|
||||
wrapper.rate = newValue
|
||||
if (automaticallyUpdateNowPlayingInfo) {
|
||||
updateNowPlayingPlaybackValues()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
+16
-9
@@ -32,8 +32,7 @@ protocol AVPlayerItemObserverDelegate: AnyObject {
|
||||
class AVPlayerItemObserver: NSObject {
|
||||
|
||||
private static var context = 0
|
||||
private let main: DispatchQueue = .main
|
||||
private let metadataOutput: AVPlayerItemMetadataOutput
|
||||
private let metadataOutput = AVPlayerItemMetadataOutput()
|
||||
|
||||
private struct AVPlayerItemKeyPath {
|
||||
static let duration = #keyPath(AVPlayerItem.duration)
|
||||
@@ -47,10 +46,8 @@ class AVPlayerItemObserver: NSObject {
|
||||
weak var delegate: AVPlayerItemObserverDelegate?
|
||||
|
||||
override init() {
|
||||
metadataOutput = AVPlayerItemMetadataOutput()
|
||||
super.init()
|
||||
|
||||
metadataOutput.setDelegate(self, queue: main)
|
||||
metadataOutput.setDelegate(self, queue: .main)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -64,22 +61,32 @@ class AVPlayerItemObserver: NSObject {
|
||||
*/
|
||||
func startObserving(item: AVPlayerItem) {
|
||||
stopObservingCurrentItem()
|
||||
isObserving = true
|
||||
observingItem = item
|
||||
|
||||
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)
|
||||
item.addObserver(self, forKeyPath: AVPlayerItemKeyPath.playbackLikelyToKeepUp, options: [.new], context: &AVPlayerItemObserver.context)
|
||||
item.add(metadataOutput)
|
||||
|
||||
// We must slightly delay adding the metadata output due to the fact that
|
||||
// stop observation is not a synchronous action and metadataOutput may not
|
||||
// be removed from last item before we try to attach it to a new one.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) { [weak self] in
|
||||
guard let `self` = self else { return }
|
||||
item.add(self.metadataOutput)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func stopObservingCurrentItem() {
|
||||
guard let observingItem = observingItem, isObserving else {
|
||||
return
|
||||
}
|
||||
|
||||
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.duration, context: &AVPlayerItemObserver.context)
|
||||
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.loadedTimeRanges, context: &AVPlayerItemObserver.context)
|
||||
observingItem.removeObserver(self, forKeyPath: AVPlayerItemKeyPath.playbackLikelyToKeepUp, context: &AVPlayerItemObserver.context)
|
||||
observingItem.remove(metadataOutput)
|
||||
|
||||
isObserving = false
|
||||
self.observingItem = nil
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftAudioEx'
|
||||
s.version = '1.0.0-rc.9'
|
||||
s.version = '1.0.0'
|
||||
s.summary = 'Easy audio streaming for iOS'
|
||||
s.description = <<-DESC
|
||||
SwiftAudioEx is an audio player written in Swift, making it simpler to work with audio playback from streams and files.
|
||||
@@ -22,5 +22,5 @@ DESC
|
||||
|
||||
s.ios.deployment_target = '11.0'
|
||||
s.swift_version = '5.0'
|
||||
s.source_files = 'SwiftAudioEx/Classes/**/*'
|
||||
s.source_files = 'Sources/SwiftAudioEx/**/*'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import XCTest
|
||||
import AVFoundation
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AVPlayerItemNotificationObserverTests: XCTestCase {
|
||||
|
||||
var item: AVPlayerItem!
|
||||
var observer: AVPlayerItemNotificationObserver!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
|
||||
observer = AVPlayerItemNotificationObserver()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
item = nil
|
||||
observer = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testObserverHasObservedItemWhenStartedObserving() {
|
||||
observer.startObserving(item: item)
|
||||
XCTAssertNotNil(observer.observingItem)
|
||||
}
|
||||
|
||||
func testObserverHasNoObservedItemWhenEndedObserving() {
|
||||
observer.startObserving(item: item)
|
||||
observer.stopObservingCurrentItem()
|
||||
XCTAssertNil(observer.observingItem)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import XCTest
|
||||
import AVFoundation
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AVPlayerItemObserverTests: XCTestCase {
|
||||
var observer: AVPlayerItemObserver!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
observer = AVPlayerItemObserver()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
observer = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testObservingItem() {
|
||||
let item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
|
||||
observer.startObserving(item: item)
|
||||
XCTAssertNotNil(observer.observingItem)
|
||||
}
|
||||
|
||||
func testIsObserving() {
|
||||
XCTAssertFalse(observer.isObserving)
|
||||
|
||||
let item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
|
||||
observer.startObserving(item: item)
|
||||
XCTAssertTrue(observer.isObserving)
|
||||
}
|
||||
|
||||
func testObservingInQuickSucccession() {
|
||||
for _ in 0...1000 {
|
||||
let item = AVPlayerItem(url: URL(fileURLWithPath: Source.path))
|
||||
observer.startObserving(item: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import XCTest
|
||||
import AVFoundation
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AVPlayerObserverTests: XCTestCase, AVPlayerObserverDelegate {
|
||||
|
||||
var status: AVPlayer.Status?
|
||||
var timeControlStatus: AVPlayer.TimeControlStatus?
|
||||
|
||||
var player: AVPlayer!
|
||||
var observer: AVPlayerObserver!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
player = AVPlayer()
|
||||
player.volume = 0.0
|
||||
observer = AVPlayerObserver()
|
||||
observer.player = player
|
||||
observer.delegate = self
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
player = nil
|
||||
observer = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testObserverIsNotObserving() {
|
||||
XCTAssertFalse(observer.isObserving)
|
||||
}
|
||||
|
||||
func testObserverIsObservingWhenObservingStarted() {
|
||||
observer.startObserving()
|
||||
XCTAssertTrue(observer.isObserving)
|
||||
}
|
||||
|
||||
func testObserverUpdatesDelegateWhenPlayerStarted() {
|
||||
observer.startObserving()
|
||||
player.replaceCurrentItem(with: AVPlayerItem(url: URL(fileURLWithPath: Source.path)))
|
||||
player.play()
|
||||
|
||||
XCTAssertNotNil(self.status)
|
||||
XCTAssertNotNil(self.timeControlStatus)
|
||||
}
|
||||
|
||||
func testObserverIsObservingWhenObservingAgain() {
|
||||
observer.startObserving()
|
||||
observer.startObserving()
|
||||
XCTAssertTrue(observer.isObserving)
|
||||
}
|
||||
|
||||
func testObserverIsNotObservingWhenObservingStopped() {
|
||||
observer.startObserving()
|
||||
observer.stopObserving()
|
||||
XCTAssertFalse(observer.isObserving)
|
||||
}
|
||||
|
||||
// MARK: - AVPlayerObserverDelegate
|
||||
|
||||
func player(statusDidChange status: AVPlayer.Status) {
|
||||
self.status = status
|
||||
}
|
||||
|
||||
func player(didChangeTimeControlStatus status: AVPlayer.TimeControlStatus) {
|
||||
self.timeControlStatus = status
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import XCTest
|
||||
import AVFoundation
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AVPlayerTimeObserverTests: XCTestCase {
|
||||
|
||||
var player: AVPlayer!
|
||||
var observer: AVPlayerTimeObserver!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
player = AVPlayer()
|
||||
player.automaticallyWaitsToMinimizeStalling = false
|
||||
player.volume = 0
|
||||
observer = AVPlayerTimeObserver(periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime())
|
||||
observer.player = player
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
player = nil
|
||||
observer = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testObserverHasBoundaryTokenWhenStartedBoundaryTimeObserving() {
|
||||
observer.registerForBoundaryTimeEvents()
|
||||
XCTAssertNotNil(observer.boundaryTimeStartObserverToken)
|
||||
}
|
||||
|
||||
func testObserverHasNoBoundaryTokenWhenEndedBoundaryTimeObserving() {
|
||||
observer.registerForBoundaryTimeEvents()
|
||||
observer.unregisterForBoundaryTimeEvents()
|
||||
XCTAssertNil(observer.boundaryTimeStartObserverToken)
|
||||
}
|
||||
|
||||
func testObserverHasPeriodicTokenWhenStartedPeriodicTimeObserving() {
|
||||
observer.registerForPeriodicTimeEvents()
|
||||
XCTAssertNotNil(observer.periodicTimeObserverToken)
|
||||
}
|
||||
|
||||
func testObserverHasNoPeriodicTokenWhenEndedPeriodicTimeObserving() {
|
||||
observer.registerForPeriodicTimeEvents()
|
||||
observer.unregisterForPeriodicEvents()
|
||||
XCTAssertNil(observer.periodicTimeObserverToken)
|
||||
}
|
||||
}
|
||||
+85
-88
@@ -1,14 +1,12 @@
|
||||
import AVFoundation
|
||||
import XCTest
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
|
||||
class AVPlayerWrapperTests: XCTestCase {
|
||||
|
||||
|
||||
var wrapper: AVPlayerWrapper!
|
||||
var holder: AVPlayerWrapperDelegateHolder!
|
||||
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
wrapper = AVPlayerWrapper()
|
||||
@@ -17,25 +15,25 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
holder = AVPlayerWrapperDelegateHolder()
|
||||
wrapper.delegate = holder
|
||||
}
|
||||
|
||||
|
||||
override func tearDown() {
|
||||
wrapper = nil
|
||||
holder = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - State tests
|
||||
|
||||
func test_AVPlayerWrapper__state__should_be_idle() {
|
||||
XCTAssert(wrapper.state == AVPlayerWrapperState.idle)
|
||||
|
||||
func testAVPlayerWrapperStateShouldBeIdle() {
|
||||
XCTAssertEqual(wrapper.state, AVPlayerWrapperState.idle)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__state__when_loading_a_source__should_be_loading() {
|
||||
|
||||
func testAVPlayerWrapperStateWhenLoadingSourceShouldBeLoading() {
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
XCTAssertEqual(wrapper.state, AVPlayerWrapperState.loading)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__state__when_loading_a_source__should_eventually_be_ready() {
|
||||
|
||||
func testAVPlayerWrapperStateWhenLoadingSourceShouldEventuallyBeReady() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
if state == .ready {
|
||||
@@ -45,8 +43,8 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__state__when_playing_a_source__should_be_playing() {
|
||||
|
||||
func testAVPlayerWrapperStateWhenPlayingSourceShouldBePlaying() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
if state == .playing {
|
||||
@@ -56,65 +54,76 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__state__when_pausing_a_source__should_be_paused() {
|
||||
|
||||
func testAVPlayerWrapperStateWhenPausingSourceShouldBePaused() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
switch state {
|
||||
case .playing: self.wrapper.pause()
|
||||
case .paused: expectation.fulfill()
|
||||
default: break
|
||||
case .playing:
|
||||
self.wrapper.pause()
|
||||
case .paused:
|
||||
expectation.fulfill()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__state__when_toggling_from_play__should_be_paused() {
|
||||
|
||||
func testAVPlayerWrapperStateWhenTogglingFromPlayShouldBePaused() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
switch state {
|
||||
case .playing: self.wrapper.togglePlaying()
|
||||
case .paused: expectation.fulfill()
|
||||
default: break
|
||||
case .playing:
|
||||
self.wrapper.togglePlaying()
|
||||
case .paused:
|
||||
expectation.fulfill()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__state__when_stopping__should_be_stopped() {
|
||||
|
||||
func testAVPlayerWrapperStateWhenStoppingShouldBeStopped() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
switch state {
|
||||
case .playing: self.wrapper.stop()
|
||||
case .stopped: expectation.fulfill()
|
||||
default: break
|
||||
case .playing:
|
||||
self.wrapper.stop()
|
||||
case .stopped:
|
||||
expectation.fulfill()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__state__loading_with_intial_time__should_be_playing() {
|
||||
|
||||
func testAVPlayerWrapperStateLoadingWithInitialTimeShouldBePlaying() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
switch state {
|
||||
case .playing: expectation.fulfill()
|
||||
default: break
|
||||
case .playing:
|
||||
expectation.fulfill()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
wrapper.load(from: LongSource.url, playWhenReady: true, initialTime: 4.0)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Duration tests
|
||||
|
||||
func test_AVPlayerWrapper__duration__should_be_0() {
|
||||
XCTAssert(wrapper.duration == 0.0)
|
||||
|
||||
func testAVPlayerWrapperDurationShouldBeZero() {
|
||||
XCTAssertEqual(wrapper.duration, 0.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__duration__loading_a_source__should_not_be_0() {
|
||||
|
||||
func testAVPlayerWrapperDurationLoadingSourceShouldNotBeZero() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { _ in
|
||||
if self.wrapper.duration > 0 {
|
||||
@@ -124,16 +133,16 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Current time tests
|
||||
|
||||
func test_AVPlayerWrapper__currentTime__should_be_0() {
|
||||
XCTAssert(wrapper.currentTime == 0)
|
||||
|
||||
func testAVPlayerWrapperCurrentTimeShouldBeZero() {
|
||||
XCTAssertEqual(wrapper.currentTime, 0)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Seeking
|
||||
|
||||
func test_AVPlayerWrapper__seeking__should_seek() {
|
||||
|
||||
func testAVPlayerWrapperSeekingShouldSeek() {
|
||||
let seekTime: TimeInterval = 5.0
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
@@ -146,7 +155,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__seeking__should_seek_while_not_yet_loaded() {
|
||||
func testAVPlayerWrapperSeekingShouldSeekWhileNotYetLoaded() {
|
||||
let seekTime: TimeInterval = 5.0
|
||||
let expectation = XCTestExpectation()
|
||||
holder.didSeekTo = { seconds in
|
||||
@@ -157,7 +166,7 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__seek_by__should_seek() {
|
||||
func testAVPlayerWrapperSeekByShouldSeek() {
|
||||
let seekTime: TimeInterval = 5.0
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
@@ -169,8 +178,8 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wrapper.load(from: Source.url, playWhenReady: false)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__loading_source_with_initial_time__should_seek() {
|
||||
|
||||
func testAVPlayerWrapperLoadingSourceWithInitialTimeShouldSeek() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.didSeekTo = { seconds in
|
||||
expectation.fulfill()
|
||||
@@ -178,14 +187,14 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wrapper.load(from: LongSource.url, playWhenReady: false, initialTime: 4.0)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Rate tests
|
||||
|
||||
func test_AVPlayerWrapper__rate__should_be_1() {
|
||||
XCTAssert(wrapper.rate == 1)
|
||||
|
||||
func testAVPlayerWrapperRateShouldBe1() {
|
||||
XCTAssertEqual(wrapper.rate, 1)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__rate__playing_a_source__should_be_1() {
|
||||
|
||||
func testAVPlayerWrapperRatePlayingSourceShouldBe1() {
|
||||
let expectation = XCTestExpectation()
|
||||
holder.stateUpdate = { state in
|
||||
if self.wrapper.rate == 1.0 {
|
||||
@@ -195,14 +204,13 @@ class AVPlayerWrapperTests: XCTestCase {
|
||||
wrapper.load(from: Source.url, playWhenReady: true)
|
||||
wait(for: [expectation], timeout: 20.0)
|
||||
}
|
||||
|
||||
func test_AVPlayerWrapper__timeObserver__when_updated__should_update_the_observers_periodicObserverTimeInterval() {
|
||||
wrapper.timeEventFrequency = .everySecond
|
||||
XCTAssert(wrapper.playerTimeObserver.periodicObserverTimeInterval == TimeEventFrequency.everySecond.getTime())
|
||||
wrapper.timeEventFrequency = .everyHalfSecond
|
||||
XCTAssert(wrapper.playerTimeObserver.periodicObserverTimeInterval == TimeEventFrequency.everyHalfSecond.getTime())
|
||||
}
|
||||
|
||||
func testAVPlayerWrapperTimeObserverWhenUpdatedShouldUpdateTheObserversPeriodicObserverTimeInterval() {
|
||||
wrapper.timeEventFrequency = .everySecond
|
||||
XCTAssertEqual(wrapper.playerTimeObserver.periodicObserverTimeInterval, TimeEventFrequency.everySecond.getTime())
|
||||
wrapper.timeEventFrequency = .everyHalfSecond
|
||||
XCTAssertEqual(wrapper.playerTimeObserver.periodicObserverTimeInterval, TimeEventFrequency.everyHalfSecond.getTime())
|
||||
}
|
||||
}
|
||||
|
||||
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
@@ -212,37 +220,29 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
)
|
||||
|
||||
func AVWrapperItemPlaybackStalled() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func AVWrapperItemFailedToPlayToEndTime() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func AVWrapper(didChangePlayWhenReady playWhenReady: Bool) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func AVWrapper(didReceiveTimedMetadata metadata: [AVTimedMetadataGroup]) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func AVWrapper(didReceiveCommonMetadata metadata: [AVMetadataItem]) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func AVWrapper(didReceiveChapterMetadata metadata: [AVTimedMetadataGroup]) {
|
||||
|
||||
}
|
||||
|
||||
func AVWrapperDidRecreateAVPlayer() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func AVWrapperItemDidPlayToEndTime() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private var _state: AVPlayerWrapperState? = nil
|
||||
var state: AVPlayerWrapperState? {
|
||||
get {
|
||||
@@ -269,28 +269,25 @@ class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
|
||||
var didUpdateDuration: ((_ duration: Double) -> Void)?
|
||||
var didSeekTo: ((_ seconds: Double) -> Void)?
|
||||
var itemDidComplete: (() -> Void)?
|
||||
|
||||
|
||||
func AVWrapper(didChangeState state: AVPlayerWrapperState) {
|
||||
self.state = state
|
||||
}
|
||||
|
||||
|
||||
func AVWrapper(secondsElapsed seconds: Double) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func AVWrapper(failedWithError error: Error?) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
func AVWrapper(seekTo seconds: Double, didFinish: Bool) {
|
||||
didSeekTo?(seconds)
|
||||
didSeekTo?(seconds)
|
||||
}
|
||||
|
||||
|
||||
func AVWrapper(didUpdateDuration duration: Double) {
|
||||
if let state = self.state {
|
||||
self.stateUpdate?(state)
|
||||
}
|
||||
didUpdateDuration?(duration)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import XCTest
|
||||
import MediaPlayer
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AudioPlayerEventTests: XCTestCase {
|
||||
|
||||
class EventListener {
|
||||
var handleEvent: ((Void)) -> Void = { _ in }
|
||||
}
|
||||
|
||||
var event: AudioPlayer.Event<(Void)>!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
event = AudioPlayer.Event()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
event = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testEventAddListener() {
|
||||
let listener = EventListener()
|
||||
event.addListener(listener, listener.handleEvent)
|
||||
waitTrue(self.event.invokers.count > 0, timeout: 5)
|
||||
}
|
||||
|
||||
func testEventRemoveListener() {
|
||||
var listener: EventListener! = EventListener()
|
||||
event.addListener(listener, listener.handleEvent)
|
||||
listener = nil
|
||||
event.emit(data: ())
|
||||
|
||||
waitEqual(self.event.invokers.count, 0, timeout: 5)
|
||||
}
|
||||
|
||||
func testEventAddMultipleListeners() {
|
||||
var listeners = [EventListener]()
|
||||
|
||||
listeners = (0..<15).map { _ in
|
||||
let listener = EventListener()
|
||||
event.addListener(listener, listener.handleEvent)
|
||||
return listener
|
||||
}
|
||||
|
||||
waitEqual(self.event.invokers.count, listeners.count, timeout: 5)
|
||||
}
|
||||
|
||||
func testEventRemoveOneListener() {
|
||||
var listeners = [EventListener]()
|
||||
|
||||
listeners = (0..<15).map { _ in
|
||||
let listener = EventListener()
|
||||
event.addListener(listener, listener.handleEvent)
|
||||
return listener
|
||||
}
|
||||
|
||||
let listenerToRemove = listeners[listeners.count / 2]
|
||||
event.removeListener(listenerToRemove)
|
||||
|
||||
waitEqual(self.event.invokers.count, listeners.count - 1, timeout: 5)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,627 @@
|
||||
import XCTest
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AudioPlayerTests: XCTestCase {
|
||||
|
||||
var audioPlayer: AudioPlayer!
|
||||
var listener: AudioPlayerEventListener!
|
||||
var playerStateEventListener: PlayerStateEventListener!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
audioPlayer = AudioPlayer()
|
||||
audioPlayer.volume = 0.0
|
||||
listener = AudioPlayerEventListener(audioPlayer: audioPlayer)
|
||||
playerStateEventListener = PlayerStateEventListener()
|
||||
audioPlayer.event.stateChange.addListener(playerStateEventListener, playerStateEventListener.handleEvent)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
audioPlayer = nil
|
||||
listener = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Load
|
||||
|
||||
func testLoadAudioItemNeverMutatesPlayWhenReadyToFalse() {
|
||||
audioPlayer.playWhenReady = true
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
XCTAssertTrue(audioPlayer.playWhenReady)
|
||||
}
|
||||
|
||||
func testLoadAudioItemNeverMutatesPlayWhenReadyToTrue() {
|
||||
audioPlayer.playWhenReady = false
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
XCTAssertFalse(audioPlayer.playWhenReady)
|
||||
}
|
||||
|
||||
func testLoadAudioItemMutatesPlayWhenReadyToFalse() {
|
||||
audioPlayer.playWhenReady = true
|
||||
XCTAssertTrue(audioPlayer.playWhenReady)
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
XCTAssertFalse(audioPlayer.playWhenReady)
|
||||
}
|
||||
|
||||
func testLoadAudioItemMutatesPlayWhenReadyToTrue() {
|
||||
audioPlayer.playWhenReady = false
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
XCTAssertTrue(audioPlayer.playWhenReady)
|
||||
}
|
||||
|
||||
func testLoadAudioItemSeeksWhenInitialTimeIsSet() {
|
||||
let expectation = XCTestExpectation(description: "Seek completion")
|
||||
|
||||
var seekCompleted = false
|
||||
listener.onSeekCompletion = {
|
||||
seekCompleted = true
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
audioPlayer.playWhenReady = false
|
||||
XCTAssertFalse(audioPlayer.playWhenReady)
|
||||
audioPlayer.load(item: FiveSecondSourceWithInitialTimeOfFourSeconds.getAudioItem())
|
||||
|
||||
wait(for: [expectation], timeout: 5)
|
||||
|
||||
XCTAssertTrue(seekCompleted)
|
||||
XCTAssertTrue(audioPlayer.currentTime >= 4)
|
||||
}
|
||||
|
||||
// MARK: - Duration
|
||||
|
||||
func testSetDurationAfterLoading() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
}
|
||||
|
||||
func testOnUpdateDurationReceivedAfterLoading() {
|
||||
let expectation = XCTestExpectation(description: "Update duration received")
|
||||
|
||||
var receivedUpdateDuration = false
|
||||
listener.onUpdateDuration = { duration in
|
||||
receivedUpdateDuration = true
|
||||
XCTAssertEqual(duration, 5, accuracy: 0.1)
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
|
||||
wait(for: [expectation], timeout: 5) // Adjust the timeout as needed
|
||||
|
||||
XCTAssertTrue(receivedUpdateDuration)
|
||||
}
|
||||
|
||||
func testResetDurationAfterLoadingAgain() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
}
|
||||
|
||||
func testResetDurationAfterReset() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
waitEqual(self.audioPlayer.duration, 5, accuracy: 0.1, timeout: 5)
|
||||
audioPlayer.clear()
|
||||
XCTAssertEqual(audioPlayer.duration, 0)
|
||||
}
|
||||
|
||||
// MARK: - Failure
|
||||
|
||||
func testFailEventOnLoadWithNonMalformedURL() {
|
||||
let expectation = XCTestExpectation(description: "Fail event received on load with non-malformed URL")
|
||||
|
||||
var didReceiveFail = false
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: "", // malformed url
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
)
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
|
||||
wait(for: [expectation], timeout: 5) // Adjust the timeout as needed
|
||||
|
||||
XCTAssertNotNil(audioPlayer.playbackError)
|
||||
XCTAssertEqual(audioPlayer.playerState, .failed)
|
||||
XCTAssertTrue(didReceiveFail)
|
||||
}
|
||||
|
||||
func testFailEventOnLoadWithNonExistingResource() {
|
||||
let expectation = XCTestExpectation(description: "Fail event received on load with non-existing resource")
|
||||
|
||||
var didReceiveFail = false
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3"
|
||||
let item = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream)
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
|
||||
wait(for: [expectation], timeout: 10) // Adjust the timeout as needed
|
||||
|
||||
XCTAssertNotNil(audioPlayer.playbackError)
|
||||
XCTAssertEqual(audioPlayer.playerState, .failed)
|
||||
XCTAssertTrue(didReceiveFail)
|
||||
}
|
||||
|
||||
func testRetryLoadingAfterFailure() {
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3"
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: nonExistingUrl,
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: 5)
|
||||
|
||||
audioPlayer.play()
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: 5)
|
||||
}
|
||||
|
||||
func testRetryLoadingAfterFailureWithPlayWhenReady() {
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3"
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: nonExistingUrl,
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: 5)
|
||||
|
||||
audioPlayer.playWhenReady = true
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: 5)
|
||||
}
|
||||
|
||||
func testRetryLoadingAfterFailureWithReload() {
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3"
|
||||
let item = DefaultAudioItem(
|
||||
audioUrl: nonExistingUrl,
|
||||
artist: "Artist",
|
||||
title: "Title",
|
||||
albumTitle: "AlbumTitle",
|
||||
sourceType: .stream
|
||||
)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed], timeout: 5)
|
||||
|
||||
audioPlayer.reload(startFromCurrentTime: true)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .failed], timeout: 5)
|
||||
}
|
||||
|
||||
func testLoadResourceSucceedsAfterPreviousFailure() {
|
||||
var didReceiveFail = false
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true
|
||||
}
|
||||
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3"
|
||||
let failItem = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream)
|
||||
|
||||
audioPlayer.load(item: failItem, playWhenReady: false)
|
||||
waitTrue(didReceiveFail, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .failed, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .failed], timeout: 5)
|
||||
|
||||
self.audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitTrue(self.audioPlayer.playbackError == nil, timeout: 5)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .failed, .loading, .playing], timeout: 5)
|
||||
}
|
||||
|
||||
func testLoadResourceSucceedsAfterPreviousFailureWithPlayWhenReady() {
|
||||
var didReceiveFail = false
|
||||
listener.onReceiveFail = { error in
|
||||
didReceiveFail = true
|
||||
}
|
||||
|
||||
let nonExistingUrl = "https://\(String.random(length: 100)).com/\(String.random(length: 100)).mp3"
|
||||
let item = DefaultAudioItem(audioUrl: nonExistingUrl, artist: "Artist", title: "Title", albumTitle: "AlbumTitle", sourceType: .stream)
|
||||
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
waitTrue(didReceiveFail, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .failed, timeout: 5)
|
||||
|
||||
self.audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitTrue(self.audioPlayer.playbackError == nil, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - States
|
||||
|
||||
func testInitialStateIsIdle() {
|
||||
XCTAssertEqual(audioPlayer.playerState, .idle)
|
||||
}
|
||||
|
||||
func testLoadingStateAfterLoadSource() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
XCTAssertEqual(audioPlayer.playerState, .loading)
|
||||
}
|
||||
|
||||
func testReadyStateAfterLoadSource() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: 5)
|
||||
}
|
||||
|
||||
func testPlayingStateAfterLoadSourceWithPlayWhenReady() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
func testReliableOrderOfEvents() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
audioPlayer.pause()
|
||||
expectedEvents.append(.paused)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
audioPlayer.play()
|
||||
expectedEvents.append(.playing)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
audioPlayer.clear()
|
||||
expectedEvents.append(.idle)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
}
|
||||
|
||||
func testUpdatePlayWhenReadyAfterExternalPause() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: 5)
|
||||
|
||||
// Simulate AVPlayer becoming paused due to external reason:
|
||||
audioPlayer.wrapper.rate = 0
|
||||
expectedEvents.append(.paused)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
XCTAssertFalse(self.audioPlayer.playWhenReady)
|
||||
}
|
||||
|
||||
func testReliableOrderOfEventsAtEndCallStop() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
audioPlayer.pause()
|
||||
expectedEvents.append(.paused)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
expectedEvents.append(.playing)
|
||||
audioPlayer.play()
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
audioPlayer.stop()
|
||||
expectedEvents.append(.stopped)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
}
|
||||
|
||||
func testReliableOrderOfEventsAfterLoadingAfterReset() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
var expectedEvents: [AVPlayerWrapperState] = [.loading, .playing]
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
audioPlayer.clear()
|
||||
expectedEvents.append(.idle)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
expectedEvents.append(contentsOf: [.loading, .playing])
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, expectedEvents, timeout: 5)
|
||||
}
|
||||
|
||||
func testPlayingStateAfterPlay() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: 5)
|
||||
|
||||
audioPlayer.play()
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
func testPausedStateAfterPause() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
|
||||
audioPlayer.pause()
|
||||
waitEqual(self.audioPlayer.playerState, .paused, timeout: 5)
|
||||
}
|
||||
|
||||
func testPausedStateAfterSettingPlayWhenReadyToFalse() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
|
||||
audioPlayer.playWhenReady = false
|
||||
waitEqual(self.audioPlayer.playerState, .paused, timeout: 5)
|
||||
}
|
||||
|
||||
func testPlayingStateAfterSettingPlayWhenReadyToTrue() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
waitEqual(self.audioPlayer.playerState, .ready, timeout: 5)
|
||||
|
||||
audioPlayer.playWhenReady = true
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
func testStoppedStateAfterStop() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - State (Current Time)
|
||||
|
||||
func testInitialCurrentTime() {
|
||||
XCTAssertEqual(audioPlayer.currentTime, 0.0)
|
||||
}
|
||||
|
||||
func testSecondsElapseEventEmittedWhenPlaying() {
|
||||
var onSecondsElapseTime = 0.0
|
||||
|
||||
audioPlayer.timeEventFrequency = .everyQuarterSecond
|
||||
listener.onSecondsElapse = { time in
|
||||
onSecondsElapseTime = time
|
||||
}
|
||||
|
||||
audioPlayer.load(item: LongSource.getAudioItem(), playWhenReady: true)
|
||||
waitTrue(onSecondsElapseTime > 0, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Buffer
|
||||
|
||||
func testAutomaticallyWaitsToMinimizeStalling() {
|
||||
XCTAssertTrue(audioPlayer.automaticallyWaitsToMinimizeStalling)
|
||||
}
|
||||
|
||||
func testBufferDurationZero() {
|
||||
XCTAssertEqual(audioPlayer.bufferDuration, 0)
|
||||
}
|
||||
|
||||
func testBufferDurationDisablesAutomaticallyWaitsToMinimizeStalling() {
|
||||
audioPlayer.bufferDuration = 1
|
||||
XCTAssertEqual(audioPlayer.bufferDuration, 1)
|
||||
XCTAssertFalse(audioPlayer.automaticallyWaitsToMinimizeStalling)
|
||||
}
|
||||
|
||||
func testEnablingAutomaticallyWaitsToMinimizeStallingSetsBufferDurationToZero() {
|
||||
audioPlayer.automaticallyWaitsToMinimizeStalling = true
|
||||
XCTAssertEqual(audioPlayer.bufferDuration, 0)
|
||||
}
|
||||
|
||||
// MARK: - Seek
|
||||
|
||||
func testSeekingBeforeLoadingComplete() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
XCTAssertTrue(audioPlayer.playerState == .loading)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.75, timeout: 5)
|
||||
}
|
||||
|
||||
func testSeekingAfterLoadingComplete() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.75, timeout: 5)
|
||||
}
|
||||
|
||||
func testSeekingWhenPaused() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: false)
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitEqual(self.audioPlayer.currentTime, 4.75, timeout: 5)
|
||||
}
|
||||
|
||||
func testSeekingWhenStopped() {
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: false)
|
||||
audioPlayer.play()
|
||||
waitForSeek(audioPlayer, to: 2)
|
||||
audioPlayer.stop()
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Rate
|
||||
|
||||
func testRateInitially() {
|
||||
XCTAssertEqual(audioPlayer.rate, 1)
|
||||
}
|
||||
|
||||
func testSpeedUpPlayback() {
|
||||
var start: Date? = nil
|
||||
var end: Date? = nil
|
||||
|
||||
listener.onPlaybackEnd = { reason in
|
||||
if reason == .playedUntilEnd {
|
||||
end = Date()
|
||||
}
|
||||
}
|
||||
|
||||
listener.onStateChange = { state in
|
||||
switch state {
|
||||
case .playing:
|
||||
if start == nil {
|
||||
start = Date()
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
audioPlayer.rate = 10
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
|
||||
if let start = start, let end = end {
|
||||
let duration = end.timeIntervalSince(start)
|
||||
XCTAssertLessThan(duration, 1, "Duration should be less than 1 second")
|
||||
}
|
||||
}
|
||||
|
||||
func testSlowDownPlayback() {
|
||||
var start: Date? = nil
|
||||
var end: Date? = nil
|
||||
|
||||
listener.onPlaybackEnd = { reason in
|
||||
if reason == .playedUntilEnd {
|
||||
end = Date()
|
||||
}
|
||||
}
|
||||
|
||||
audioPlayer.rate = 0.5
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
|
||||
listener.onStateChange = { state in
|
||||
switch state {
|
||||
case .playing:
|
||||
if start == nil {
|
||||
start = Date()
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
audioPlayer.seek(to: 4.75)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
|
||||
if let start = start, let end = end {
|
||||
let duration = end.timeIntervalSince(start)
|
||||
XCTAssertLessThanOrEqual(duration, 1, "Duration should be less than or equal to 1 second")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Current Item
|
||||
|
||||
func testCurrentItemInitially() {
|
||||
XCTAssertNil(audioPlayer.currentItem, "Current item should be nil initially")
|
||||
}
|
||||
|
||||
func testCurrentItemAfterLoading() {
|
||||
audioPlayer.load(item: Source.getAudioItem(), playWhenReady: false)
|
||||
XCTAssertEqual(audioPlayer.currentItem?.getSourceUrl(), Source.getAudioItem().getSourceUrl(), "Current item should not be nil after loading")
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerStateEventListener {
|
||||
private let lockQueue = DispatchQueue(
|
||||
label: "PlayerStateEventListener.lockQueue",
|
||||
target: .global()
|
||||
)
|
||||
var _states: [AudioPlayerState] = []
|
||||
var states: [AudioPlayerState] {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _states
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
lockQueue.sync {
|
||||
_states = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
private var _statesWithoutBuffering: [AudioPlayerState] = []
|
||||
var statesWithoutBuffering: [AudioPlayerState] {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _statesWithoutBuffering
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
lockQueue.sync {
|
||||
_statesWithoutBuffering = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
func handleEvent(state: AudioPlayerState) {
|
||||
states.append(state)
|
||||
if (state != .ready && state != .buffering && (statesWithoutBuffering.isEmpty || statesWithoutBuffering.last != state)) {
|
||||
statesWithoutBuffering.append(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AudioPlayerEventListener {
|
||||
|
||||
var state: AudioPlayerState?
|
||||
|
||||
var onStateChange: ((_ state: AudioPlayerState) -> Void)?
|
||||
var onSecondsElapse: ((_ seconds: TimeInterval) -> Void)?
|
||||
var onSeekCompletion: (() -> Void)?
|
||||
var onReceiveFail: ((_ error: Error?) -> Void)?
|
||||
var onPlaybackEnd: ((_: AudioPlayer.PlaybackEndEventData) -> Void)?
|
||||
var onUpdateDuration: ((_: AudioPlayer.UpdateDurationEventData) -> Void)?
|
||||
|
||||
weak var audioPlayer: AudioPlayer?
|
||||
|
||||
init(audioPlayer: AudioPlayer) {
|
||||
audioPlayer.event.updateDuration.addListener(self, handleUpdateDuration)
|
||||
audioPlayer.event.stateChange.addListener(self, handleStateChange)
|
||||
audioPlayer.event.seek.addListener(self, handleSeek)
|
||||
audioPlayer.event.secondElapse.addListener(self, handleSecondsElapse)
|
||||
audioPlayer.event.fail.addListener(self, handleFail)
|
||||
audioPlayer.event.playbackEnd.addListener(self, handlePlaybackEnd)
|
||||
}
|
||||
|
||||
deinit {
|
||||
audioPlayer?.event.stateChange.removeListener(self)
|
||||
audioPlayer?.event.seek.removeListener(self)
|
||||
audioPlayer?.event.secondElapse.removeListener(self)
|
||||
}
|
||||
|
||||
func handleStateChange(state: AudioPlayerState) {
|
||||
self.state = state
|
||||
onStateChange?(state)
|
||||
}
|
||||
|
||||
func handleSeek(data: AudioPlayer.SeekEventData) {
|
||||
onSeekCompletion?()
|
||||
}
|
||||
|
||||
func handleSecondsElapse(data: AudioPlayer.SecondElapseEventData) {
|
||||
self.onSecondsElapse?(data)
|
||||
}
|
||||
|
||||
func handleFail(error: Error?) {
|
||||
self.onReceiveFail?(error)
|
||||
}
|
||||
|
||||
func handlePlaybackEnd(_ data: AudioPlayer.PlaybackEndEventData) {
|
||||
self.onPlaybackEnd?(data)
|
||||
}
|
||||
|
||||
func handleUpdateDuration(_ data: AudioPlayer.UpdateDurationEventData) {
|
||||
self.onUpdateDuration?(data)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
static func random(length: Int = 20) -> String {
|
||||
let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
var randomString: String = ""
|
||||
|
||||
for _ in 0..<length {
|
||||
let randomValue = arc4random_uniform(UInt32(base.count))
|
||||
randomString += "\(base[base.index(base.startIndex, offsetBy: Int(randomValue))])"
|
||||
}
|
||||
return randomString
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import XCTest
|
||||
import AVFoundation
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class AudioSessionControllerTests: XCTestCase {
|
||||
var audioSessionController: AudioSessionController!
|
||||
var delegate: AudioSessionControllerDelegateImplementation!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
audioSessionController = AudioSessionController(audioSession: NonFailingAudioSession())
|
||||
delegate = AudioSessionControllerDelegateImplementation()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
audioSessionController = nil
|
||||
delegate = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testAudioSessionIsInactive() {
|
||||
XCTAssertFalse(audioSessionController.audioSessionIsActive)
|
||||
}
|
||||
|
||||
func testActivateSession() {
|
||||
do {
|
||||
try audioSessionController.activateSession()
|
||||
XCTAssertTrue(audioSessionController.audioSessionIsActive)
|
||||
} catch {
|
||||
XCTFail("Failed to activate session: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testDeactivateSession() {
|
||||
do {
|
||||
try audioSessionController.activateSession()
|
||||
try audioSessionController.deactivateSession()
|
||||
XCTAssertFalse(audioSessionController.audioSessionIsActive)
|
||||
} catch {
|
||||
XCTFail("Failed to deactivate session: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testIsObservingForInterruptions() {
|
||||
XCTAssertTrue(audioSessionController.isObservingForInterruptions)
|
||||
}
|
||||
|
||||
func testIsObservingForInterruptionsFalse() {
|
||||
audioSessionController.isObservingForInterruptions = false
|
||||
XCTAssertFalse(audioSessionController.isObservingForInterruptions)
|
||||
}
|
||||
|
||||
func testInterruptionEnded() {
|
||||
let notification = Notification(
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
object: nil,
|
||||
userInfo: [
|
||||
AVAudioSessionInterruptionTypeKey: UInt(0),
|
||||
AVAudioSessionInterruptionOptionKey: UInt(1),
|
||||
]
|
||||
)
|
||||
audioSessionController.delegate = delegate
|
||||
audioSessionController.handleInterruption(notification: notification)
|
||||
XCTAssertEqual(delegate.interruptionType, .ended(shouldResume: true))
|
||||
}
|
||||
|
||||
func testInterruptionBegan() {
|
||||
let notification = Notification(
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
object: nil,
|
||||
userInfo: [AVAudioSessionInterruptionTypeKey: UInt(1)]
|
||||
)
|
||||
audioSessionController.delegate = delegate
|
||||
audioSessionController.handleInterruption(notification: notification)
|
||||
XCTAssertEqual(delegate.interruptionType, .began)
|
||||
}
|
||||
|
||||
func testAudioSessionIsInactiveWithFailingAudioSession() {
|
||||
audioSessionController = AudioSessionController(audioSession: FailingAudioSession())
|
||||
try? audioSessionController.activateSession()
|
||||
XCTAssertFalse(audioSessionController.audioSessionIsActive)
|
||||
}
|
||||
}
|
||||
|
||||
class AudioSessionControllerDelegateImplementation: AudioSessionControllerDelegate {
|
||||
var interruptionType: InterruptionType? = nil
|
||||
|
||||
func handleInterruption(type: InterruptionType) {
|
||||
self.interruptionType = type
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import XCTest
|
||||
import MediaPlayer
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class NowPlayingInfoControllerTests: XCTestCase {
|
||||
|
||||
var nowPlayingController: NowPlayingInfoController!
|
||||
var infoCenter: NowPlayingInfoCenter_Mock!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
infoCenter = NowPlayingInfoCenter_Mock()
|
||||
nowPlayingController = NowPlayingInfoController(dispatchQueue: MockDispatchQueue(), infoCenter: infoCenter)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
infoCenter = nil
|
||||
nowPlayingController = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testInfoDictionaryNotEmpty() {
|
||||
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
|
||||
XCTAssertGreaterThan(nowPlayingController.info.count, 0)
|
||||
}
|
||||
|
||||
func testInfoDictionaryEmptyAfterClear() {
|
||||
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
|
||||
nowPlayingController.clear()
|
||||
XCTAssertEqual(nowPlayingController.info.count, 0)
|
||||
}
|
||||
|
||||
func testInfoCenterNotNil() {
|
||||
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
|
||||
XCTAssertNotNil(nowPlayingController.infoCenter.nowPlayingInfo)
|
||||
}
|
||||
|
||||
func testInfoCenterNotEmpty() {
|
||||
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
|
||||
XCTAssertGreaterThan(nowPlayingController.infoCenter.nowPlayingInfo?.count ?? 0, 0)
|
||||
}
|
||||
|
||||
func testInfoCenterEmptyAfterClear() {
|
||||
nowPlayingController.set(keyValue: MediaItemProperty.title("Some title"))
|
||||
nowPlayingController.clear()
|
||||
XCTAssertNil(nowPlayingController.infoCenter.nowPlayingInfo)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import XCTest
|
||||
import MediaPlayer
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class NowPlayingInfoTests: XCTestCase {
|
||||
|
||||
var audioPlayer: AudioPlayer!
|
||||
var nowPlayingController: NowPlayingInfoController_Mock!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
nowPlayingController = NowPlayingInfoController_Mock()
|
||||
audioPlayer = AudioPlayer(nowPlayingInfoController: nowPlayingController)
|
||||
audioPlayer.automaticallyUpdateNowPlayingInfo = true
|
||||
audioPlayer.volume = 0
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
audioPlayer = nil
|
||||
nowPlayingController = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testNowPlayingInfoControllerMetadataUpdate() {
|
||||
let item = Source.getAudioItem()
|
||||
audioPlayer.load(item: item, playWhenReady: false)
|
||||
|
||||
XCTAssertEqual(nowPlayingController.getTitle(), item.getTitle())
|
||||
XCTAssertEqual(nowPlayingController.getArtist(), item.getArtist())
|
||||
XCTAssertEqual(nowPlayingController.getAlbumTitle(), item.getAlbumTitle())
|
||||
XCTAssertNotNil(nowPlayingController.getArtwork())
|
||||
}
|
||||
|
||||
func testNowPlayingInfoControllerPlaybackValuesUpdate() {
|
||||
let item = LongSource.getAudioItem()
|
||||
audioPlayer.load(item: item, playWhenReady: true)
|
||||
|
||||
XCTAssertNotNil(nowPlayingController.getRate())
|
||||
XCTAssertNotNil(nowPlayingController.getDuration())
|
||||
XCTAssertNotNil(nowPlayingController.getCurrentTime())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
import XCTest
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class QueueManagerTests: XCTestCase {
|
||||
|
||||
let dummyItem = 0
|
||||
let items: [Int] = [0, 1, 2]
|
||||
var queue: QueueManager<Int>!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
queue = QueueManager()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
queue = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Current Item
|
||||
|
||||
func testCurrentItemOnStart() {
|
||||
XCTAssertNil(queue.current)
|
||||
}
|
||||
|
||||
func testOneItemAdded() {
|
||||
queue.add(dummyItem)
|
||||
XCTAssertNil(queue.current)
|
||||
}
|
||||
|
||||
func testOneItemAddedAndJumping() {
|
||||
queue.add(dummyItem)
|
||||
try! queue.jump(to: 0)
|
||||
}
|
||||
|
||||
func testOneItemAddedAndJumpingAndReplacing() {
|
||||
queue.add(self.dummyItem)
|
||||
try! queue.jump(to: 0)
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
XCTAssertEqual(queue.current, 1)
|
||||
}
|
||||
|
||||
func testReplacingCurrentItemWithEmptyQueue() {
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
XCTAssertNotNil(queue.current)
|
||||
}
|
||||
|
||||
func testAddingItemsAndJumpingToLast() {
|
||||
queue.add(self.items)
|
||||
try! queue.jump(to: queue.items.count - 1)
|
||||
XCTAssertNotNil(queue.current)
|
||||
}
|
||||
|
||||
// MARK: - Adding At Index
|
||||
|
||||
func testAddingItemAtIndexZeroWhenQueueIsEmpty() {
|
||||
try! queue.add([3], at: 0)
|
||||
XCTAssertEqual(queue.items.first, 3)
|
||||
XCTAssertNil(queue.current)
|
||||
XCTAssertEqual(queue.currentIndex, -1)
|
||||
}
|
||||
|
||||
func testAddingItemAtIndexAndJumpingToFirstItem() {
|
||||
queue.add([1, 2])
|
||||
try! queue.jump(to: 0)
|
||||
XCTAssertEqual(queue.items.last, 2)
|
||||
}
|
||||
|
||||
func testAddingItemAtCurrentElementCount() {
|
||||
queue.add([1, 2])
|
||||
try! queue.jump(to: 0)
|
||||
try! queue.add([3, 4, 5], at: queue.items.count)
|
||||
XCTAssertEqual(queue.items.last, 5)
|
||||
}
|
||||
|
||||
func testAddingItemBeforeTheFirstItem() {
|
||||
queue.add([1, 2])
|
||||
try! queue.jump(to: 0)
|
||||
try! queue.add([-1], at: 0)
|
||||
XCTAssertEqual(queue.items.first, -1)
|
||||
}
|
||||
|
||||
func testAddingItemAfterTheLastItem() {
|
||||
queue.add([1, 2])
|
||||
try! queue.jump(to: 0)
|
||||
try! queue.add([6], at: queue.items.count)
|
||||
XCTAssertEqual(queue.items.last, 6)
|
||||
}
|
||||
|
||||
func testAddingItemAtCurrentIndex() {
|
||||
queue.add([1, 2])
|
||||
try! queue.jump(to: 0)
|
||||
|
||||
queue.next()
|
||||
try! queue.add([5], at: queue.currentIndex)
|
||||
|
||||
XCTAssertEqual(queue.current, 2)
|
||||
XCTAssertEqual(queue.currentIndex, 2)
|
||||
}
|
||||
|
||||
// MARK: - Add Item (No Jump)
|
||||
|
||||
func testAddOneItemWithoutJumping() {
|
||||
queue.add(0)
|
||||
XCTAssertEqual(queue.items.count, 1)
|
||||
}
|
||||
|
||||
func testReplaceItem() {
|
||||
queue.add(0)
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
XCTAssertEqual(queue.items.count, 2)
|
||||
XCTAssertEqual(queue.current, 1)
|
||||
XCTAssertEqual(queue.currentIndex, 1)
|
||||
}
|
||||
|
||||
func testCallingNextAfterReplacement() {
|
||||
queue.add(0)
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
let item = queue.next()
|
||||
XCTAssertEqual(item, 1)
|
||||
}
|
||||
|
||||
func testCallingPreviousAfterReplacement() {
|
||||
queue.add(0)
|
||||
queue.replaceCurrentItem(with: 1)
|
||||
let item = queue.previous()
|
||||
XCTAssertEqual(item, 0)
|
||||
}
|
||||
|
||||
func testCallingNext() {
|
||||
queue.add(0)
|
||||
queue.next()
|
||||
let item = queue.next()
|
||||
XCTAssertNil(item)
|
||||
}
|
||||
|
||||
func testCallingPrevious() {
|
||||
queue.add(0)
|
||||
queue.previous()
|
||||
let item = queue.previous()
|
||||
XCTAssertNil(item)
|
||||
}
|
||||
|
||||
func testJumpToZeroAndCallNextWithWrap() {
|
||||
queue.add(0)
|
||||
try! queue.jump(to: 0)
|
||||
let nextIndex = queue.next(wrap: true)
|
||||
XCTAssertEqual(nextIndex, 0)
|
||||
}
|
||||
|
||||
func testJumpToZeroAndCallPreviousWithWrap() {
|
||||
queue.add(0)
|
||||
try! queue.jump(to: 0)
|
||||
let previousIndex = queue.previous(wrap: true)
|
||||
XCTAssertEqual(previousIndex, 0)
|
||||
}
|
||||
|
||||
// MARK: - Adding Multiple Items
|
||||
|
||||
func testAddMultipleItems() {
|
||||
queue.add(items)
|
||||
XCTAssertEqual(queue.items.count, items.count)
|
||||
XCTAssertNil(queue.current)
|
||||
XCTAssertEqual(queue.nextItems.count, 0)
|
||||
}
|
||||
|
||||
func testQueueNavigation() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
let nextItem = queue.next()
|
||||
|
||||
XCTAssertEqual(nextItem, items[1])
|
||||
XCTAssertEqual(queue.current, items[1])
|
||||
XCTAssertNotNil(queue.previousItems)
|
||||
|
||||
// Previous
|
||||
XCTAssertEqual(queue.previous(), 0)
|
||||
XCTAssertEqual(queue.current, items.first)
|
||||
|
||||
// Previous at start of queue
|
||||
XCTAssertEqual(queue.previous(), 0)
|
||||
|
||||
// Previous at start of queue with wrap
|
||||
let index3 = queue.previous(wrap: true)
|
||||
XCTAssertEqual(index3, items.count - 1)
|
||||
XCTAssertEqual(queue.currentIndex, items.count - 1)
|
||||
XCTAssertEqual(queue.current, items.last)
|
||||
|
||||
// Next at end of queue
|
||||
let index4 = queue.next()
|
||||
XCTAssertEqual(index4, items.count - 1)
|
||||
|
||||
// Next at end of queue with wrap
|
||||
let index5 = queue.next(wrap: true)
|
||||
XCTAssertEqual(index5, 0)
|
||||
XCTAssertEqual(queue.currentIndex, 0)
|
||||
XCTAssertEqual(queue.current, items.first)
|
||||
}
|
||||
|
||||
func testRemovePreviousItemsAfterNext() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
queue.next()
|
||||
queue.removePreviousItems()
|
||||
|
||||
XCTAssertEqual(queue.previousItems.count, 0)
|
||||
XCTAssertEqual(queue.currentIndex, 0)
|
||||
}
|
||||
|
||||
func testAddMoreItems() {
|
||||
queue.add(items)
|
||||
let initialItemCount = queue.items.count
|
||||
try? queue.add([10, 11, 12, 13], at: queue.items.endIndex - 1)
|
||||
XCTAssertEqual(queue.items.count, initialItemCount + 4)
|
||||
}
|
||||
|
||||
func testAddMoreItemsAtSmallerIndex() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
let initialCurrentIndex = queue.currentIndex
|
||||
try? queue.add([10, 11, 12, 13], at: initialCurrentIndex)
|
||||
XCTAssertEqual(queue.currentIndex, initialCurrentIndex + 4)
|
||||
}
|
||||
|
||||
// MARK: - Remove
|
||||
|
||||
func testRemoveItemAtIndexLessThanCurrent() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 1)
|
||||
|
||||
let initialCurrentIndex = queue.currentIndex
|
||||
let removed = try? queue.removeItem(at: initialCurrentIndex - 1)
|
||||
|
||||
XCTAssertEqual(removed, 0)
|
||||
XCTAssertEqual(initialCurrentIndex, 1)
|
||||
XCTAssertEqual(queue.currentIndex, 0)
|
||||
}
|
||||
|
||||
func testRemoveSecondItem() {
|
||||
queue.add(items)
|
||||
let removed = try? queue.removeItem(at: 1)
|
||||
XCTAssertNotNil(removed)
|
||||
XCTAssertEqual(queue.items.count, items.count - 1)
|
||||
}
|
||||
|
||||
func testRemoveLastItem() {
|
||||
queue.add(items)
|
||||
let removed = try? queue.removeItem(at: items.count - 1)
|
||||
XCTAssertNotNil(removed)
|
||||
XCTAssertEqual(queue.items.count, items.count - 1)
|
||||
}
|
||||
|
||||
func testRemoveCurrentItemWhenFirstItem() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
let removed = try? queue.removeItem(at: queue.currentIndex)
|
||||
|
||||
XCTAssertNotNil(removed)
|
||||
XCTAssertEqual(queue.items.count, items.count - 1)
|
||||
XCTAssertEqual(queue.currentIndex, 0)
|
||||
XCTAssertEqual(queue.current, 1)
|
||||
}
|
||||
|
||||
func testRemoveCurrentItemWhenLastItem() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: items.count - 1)
|
||||
let removed = try? queue.removeItem(at: queue.currentIndex)
|
||||
|
||||
XCTAssertNotNil(removed)
|
||||
XCTAssertEqual(queue.items.count, items.count - 1)
|
||||
XCTAssertEqual(queue.currentIndex, 0)
|
||||
}
|
||||
|
||||
func testRemoveWithTooLargeIndex() {
|
||||
queue.add(items)
|
||||
let removed = try? queue.removeItem(at: items.count)
|
||||
XCTAssertNil(removed)
|
||||
XCTAssertEqual(queue.items.count, items.count)
|
||||
}
|
||||
|
||||
func testRemoveWithTooSmallIndex() {
|
||||
queue.add(items)
|
||||
let removed = try? queue.removeItem(at: -1)
|
||||
XCTAssertNil(removed)
|
||||
XCTAssertEqual(queue.items.count, items.count)
|
||||
}
|
||||
|
||||
func testRemoveUpcomingItems() {
|
||||
queue.add(items)
|
||||
queue.removeUpcomingItems()
|
||||
XCTAssertEqual(queue.nextItems.count, 0)
|
||||
}
|
||||
|
||||
// MARK: - Jump
|
||||
|
||||
// Test case 22: jumping to the current item
|
||||
func testJumpToCurrentItem() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
|
||||
let item = try! queue.jump(to: queue.currentIndex)
|
||||
XCTAssertNotNil(item)
|
||||
}
|
||||
|
||||
func testJumpToSecondItem() {
|
||||
queue.add(items)
|
||||
let _ = try? queue.jump(to: 1)
|
||||
let jumped = try? queue.jump(to: 1)
|
||||
XCTAssertNotNil(jumped)
|
||||
XCTAssertEqual(jumped, queue.current)
|
||||
XCTAssertEqual(queue.currentIndex, 1)
|
||||
}
|
||||
|
||||
func testJumpToLastItem() {
|
||||
queue.add(items)
|
||||
let jumped = try? queue.jump(to: items.count - 1)
|
||||
XCTAssertNotNil(jumped)
|
||||
XCTAssertEqual(jumped, queue.current)
|
||||
XCTAssertEqual(queue.currentIndex, items.count - 1)
|
||||
}
|
||||
|
||||
func testJumpToNegativeIndex() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
let jumped = try? queue.jump(to: -1)
|
||||
XCTAssertNil(jumped)
|
||||
XCTAssertEqual(queue.currentIndex, 0)
|
||||
}
|
||||
|
||||
func testJumpWithTooLargeIndex() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
let jumped = try? queue.jump(to: items.count)
|
||||
XCTAssertNil(jumped)
|
||||
XCTAssertEqual(queue.currentIndex, 0)
|
||||
}
|
||||
|
||||
// MARK: - Moving
|
||||
|
||||
func testMoveItemUpOne() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
try! queue.moveItem(fromIndex: queue.currentIndex, toIndex: queue.currentIndex + 1)
|
||||
XCTAssertEqual(queue.currentIndex, 1)
|
||||
}
|
||||
|
||||
func testMoveFromNegativeIndex() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
|
||||
XCTAssertThrowsError(try queue.moveItem(fromIndex: -1, toIndex: queue.currentIndex + 1))
|
||||
}
|
||||
|
||||
func testMoveFromTooLargeIndex() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
|
||||
XCTAssertThrowsError(try queue.moveItem(fromIndex: queue.items.count, toIndex: queue.currentIndex + 1))
|
||||
}
|
||||
|
||||
func testMoveToNegativeIndex() {
|
||||
queue.add(items)
|
||||
try! queue.jump(to: 0)
|
||||
|
||||
XCTAssertThrowsError(try queue.moveItem(fromIndex: queue.currentIndex + 1, toIndex: -1))
|
||||
}
|
||||
|
||||
func testMoveToTooLargeIndex() {
|
||||
queue.add(items)
|
||||
try! queue.moveItem(fromIndex: 0, toIndex: queue.items.count)
|
||||
XCTAssertEqual(queue.items.last, 0)
|
||||
XCTAssertEqual(queue.items.first, 1)
|
||||
}
|
||||
|
||||
func testMoveSecondToThird() {
|
||||
queue.add(items)
|
||||
try? queue.moveItem(fromIndex: 1, toIndex: 3)
|
||||
XCTAssertEqual(queue.items, [0, 2, 1])
|
||||
}
|
||||
|
||||
// MARK: - Clear
|
||||
|
||||
func testClearQueue() {
|
||||
queue.add(items)
|
||||
queue.clearQueue()
|
||||
XCTAssertEqual(queue.currentIndex, -1)
|
||||
XCTAssertEqual(queue.items.count, 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
import XCTest
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
class QueuedAudioPlayerTests: XCTestCase {
|
||||
|
||||
var audioPlayer: QueuedAudioPlayer!
|
||||
var currentItemEventListener: QueuedAudioPlayer.CurrentItemEventListener!
|
||||
var playbackEndEventListener: QueuedAudioPlayer.PlaybackEndEventListener!
|
||||
var playerStateEventListener: QueuedAudioPlayer.PlayerStateEventListener!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
audioPlayer = QueuedAudioPlayer()
|
||||
|
||||
currentItemEventListener = QueuedAudioPlayer.CurrentItemEventListener()
|
||||
audioPlayer.event.currentItem.addListener(
|
||||
currentItemEventListener,
|
||||
currentItemEventListener.handleEvent
|
||||
)
|
||||
|
||||
playbackEndEventListener = QueuedAudioPlayer.PlaybackEndEventListener()
|
||||
audioPlayer.event.playbackEnd.addListener(
|
||||
playbackEndEventListener,
|
||||
playbackEndEventListener.handleEvent
|
||||
)
|
||||
|
||||
playerStateEventListener = QueuedAudioPlayer.PlayerStateEventListener()
|
||||
audioPlayer.event.stateChange.addListener(
|
||||
playerStateEventListener,
|
||||
playerStateEventListener.handleEvent
|
||||
)
|
||||
|
||||
audioPlayer.volume = 0.0
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
audioPlayer.event.currentItem.removeListener(currentItemEventListener)
|
||||
currentItemEventListener = nil
|
||||
|
||||
audioPlayer.event.playbackEnd.removeListener(playbackEndEventListener)
|
||||
playbackEndEventListener = nil
|
||||
|
||||
audioPlayer.event.stateChange.removeListener(playerStateEventListener)
|
||||
playerStateEventListener = nil
|
||||
|
||||
audioPlayer = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Current Item
|
||||
|
||||
func testCurrentItemOnCreate() {
|
||||
XCTAssertNil(audioPlayer.currentItem)
|
||||
}
|
||||
|
||||
func testAddingOneItem() {
|
||||
audioPlayer.add(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertNotNil(audioPlayer.currentItem)
|
||||
}
|
||||
|
||||
func testLoadItemAfterAdding() {
|
||||
testAddingOneItem()
|
||||
let item = Source.getAudioItem()
|
||||
audioPlayer.load(item: item)
|
||||
|
||||
XCTAssertEqual(audioPlayer.currentItem?.getSourceUrl(), item.getSourceUrl())
|
||||
}
|
||||
|
||||
func testRemovingItemAfterAdding() {
|
||||
audioPlayer.add(item: FiveSecondSource.getAudioItem())
|
||||
audioPlayer.repeatMode = RepeatMode.track
|
||||
audioPlayer.play()
|
||||
audioPlayer.seek(to: 4)
|
||||
try? audioPlayer.removeItem(at: audioPlayer.currentIndex)
|
||||
|
||||
XCTAssertNil(audioPlayer.currentItem)
|
||||
XCTAssertEqual(audioPlayer.playerState, AudioPlayerState.idle)
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .idle], timeout: 5)
|
||||
}
|
||||
|
||||
func testLoadAfterRemoval() {
|
||||
testRemovingItemAfterAdding()
|
||||
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
XCTAssertNotEqual(audioPlayer.currentItem?.getSourceUrl(), FiveSecondSource.getAudioItem().getSourceUrl())
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .idle, .loading, .playing], timeout: 5)
|
||||
XCTAssertEqual(audioPlayer.playerState, AudioPlayerState.playing)
|
||||
}
|
||||
|
||||
func testAddingMultipleItems() {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), ShortSource.getAudioItem()], playWhenReady: false)
|
||||
XCTAssertNotNil(audioPlayer.currentItem)
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 0)
|
||||
}
|
||||
|
||||
func testRemoveItemAfterAddingMultiple() {
|
||||
testAddingMultipleItems()
|
||||
|
||||
try? audioPlayer.removeItem(at: 0)
|
||||
XCTAssertEqual(audioPlayer.items.count, 1)
|
||||
XCTAssertEqual(audioPlayer.currentItem?.getSourceUrl(), ShortSource.getAudioItem().getSourceUrl())
|
||||
}
|
||||
|
||||
// MARK: - Next Items
|
||||
|
||||
func testNextItemsEmptyOnCreate() {
|
||||
XCTAssertTrue(audioPlayer.nextItems.isEmpty)
|
||||
}
|
||||
|
||||
func testNextItemsAfterAddingTwoItems() {
|
||||
audioPlayer.add(items: [Source.getAudioItem(), Source.getAudioItem()])
|
||||
XCTAssertEqual(audioPlayer.nextItems.count, 1)
|
||||
}
|
||||
|
||||
func testNextItemsWhileNavigationAfterAddingTwoItems() {
|
||||
testNextItemsAfterAddingTwoItems()
|
||||
|
||||
// Test next
|
||||
audioPlayer.next()
|
||||
XCTAssertEqual(audioPlayer.nextItems.count, 0)
|
||||
|
||||
// Test previous
|
||||
audioPlayer.previous()
|
||||
XCTAssertEqual(audioPlayer.nextItems.count, 1)
|
||||
}
|
||||
|
||||
func testRemovingOneItem() {
|
||||
let queue = [Source.getAudioItem(), Source.getAudioItem()]
|
||||
audioPlayer.add(items: queue)
|
||||
try? audioPlayer.removeItem(at: queue.count - 1)
|
||||
XCTAssertEqual(audioPlayer.nextItems.count, queue.count - 2)
|
||||
}
|
||||
|
||||
func testJumpingToLastItem() {
|
||||
let queue = [Source.getAudioItem(), Source.getAudioItem()]
|
||||
audioPlayer.add(items: queue)
|
||||
try? audioPlayer.jumpToItem(atIndex: queue.count - 1)
|
||||
XCTAssertTrue(audioPlayer.nextItems.isEmpty)
|
||||
}
|
||||
|
||||
func testRemovingUpcomingItems() {
|
||||
audioPlayer.add(items: [Source.getAudioItem(), Source.getAudioItem()])
|
||||
audioPlayer.removeUpcomingItems()
|
||||
XCTAssertTrue(audioPlayer.nextItems.isEmpty)
|
||||
}
|
||||
|
||||
func testStopping() {
|
||||
audioPlayer.add(items: [Source.getAudioItem(), Source.getAudioItem()])
|
||||
audioPlayer.stop()
|
||||
XCTAssertEqual(audioPlayer.nextItems.count, 1)
|
||||
}
|
||||
|
||||
// MARK: - Previous Items
|
||||
|
||||
func testPreviousItemsEmptyOnCreate() {
|
||||
XCTAssertTrue(audioPlayer.previousItems.isEmpty)
|
||||
}
|
||||
|
||||
func testPreviousItemsAfterAddingTwoItems() {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
XCTAssertTrue(audioPlayer.previousItems.isEmpty)
|
||||
}
|
||||
|
||||
func testPreviousItemsWhileNavigationAfterAddingTwoItems() {
|
||||
testPreviousItemsAfterAddingTwoItems()
|
||||
|
||||
// Test next
|
||||
audioPlayer.next()
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .paused, .loading, .paused], timeout: 5)
|
||||
XCTAssertEqual(audioPlayer.previousItems.count, 1)
|
||||
waitEqual(self.playbackEndEventListener.lastReason, .skippedToNext, timeout: 5)
|
||||
|
||||
// Test stop
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: 5)
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: 5)
|
||||
|
||||
// Test stop again
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.audioPlayer.playerState, .stopped, timeout: 5)
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: 5)
|
||||
|
||||
// Test previous
|
||||
audioPlayer.previous()
|
||||
waitEqual(self.audioPlayer.playerState, .loading, timeout: 5)
|
||||
// should not have emitted playbackEnd .skippedToPrevious because playback was already stopped previously
|
||||
waitEqual(self.playbackEndEventListener.reasons, [.skippedToNext, .playerStopped], timeout: 5)
|
||||
|
||||
}
|
||||
|
||||
func testRemoveAllPreviousItems() {
|
||||
testPreviousItemsAfterAddingTwoItems()
|
||||
audioPlayer.removePreviousItems()
|
||||
XCTAssertEqual(audioPlayer.previousItems.count, 0)
|
||||
}
|
||||
|
||||
// MARK: - Pause
|
||||
|
||||
func testPauseWithPlayWhenReadyTrue() {
|
||||
audioPlayer.playWhenReady = true
|
||||
XCTAssertTrue(audioPlayer.playWhenReady)
|
||||
}
|
||||
|
||||
func testPauseOnEmptyQueue() {
|
||||
audioPlayer.playWhenReady = true
|
||||
audioPlayer.pause()
|
||||
XCTAssertFalse(audioPlayer.playWhenReady)
|
||||
|
||||
// It should not have mutated player state to .paused because playback was already idle
|
||||
XCTAssertEqual(playerStateEventListener.states, [])
|
||||
}
|
||||
|
||||
func testPauseWithItemAndPausingDirectly() {
|
||||
audioPlayer.playWhenReady = true
|
||||
|
||||
// Adding an item and pausing directly
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.pause()
|
||||
|
||||
// It should have gone into .paused state from .loading and then into .ready because playback can be started
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .paused, .ready], timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Stop
|
||||
|
||||
func testStopOnEmptyQueue() {
|
||||
audioPlayer.stop()
|
||||
waitEqual(self.playerStateEventListener.states, [.stopped], timeout: 5)
|
||||
|
||||
// It should not have emitted a playbackEnd event
|
||||
XCTAssertNil(playbackEndEventListener.lastReason)
|
||||
}
|
||||
|
||||
func testStopWithTwoItems() {
|
||||
audioPlayer.add(items: [
|
||||
FiveSecondSource.getAudioItem(),
|
||||
FiveSecondSource.getAudioItem()
|
||||
])
|
||||
audioPlayer.stop()
|
||||
|
||||
// It should have emitted a playbackEnd .playerStopped event
|
||||
waitEqual(self.playbackEndEventListener.lastReason, .playerStopped, timeout: 5)
|
||||
|
||||
// It should have mutated player state from .loading to .stopped
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .stopped], timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Load
|
||||
|
||||
func testLoadItemOnEmptyQueue() {
|
||||
// Calling load(item) on an empty queue should set currentItem
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertNotNil(audioPlayer.currentItem)
|
||||
|
||||
// It should have started loading, but not playing yet
|
||||
waitEqual(self.playerStateEventListener.states, [.loading, .paused, .ready], timeout: 5)
|
||||
}
|
||||
|
||||
func testLoadItemAfterPlaying() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.load(item: FiveSecondSource.getAudioItem())
|
||||
XCTAssertNotNil(audioPlayer.currentItem)
|
||||
|
||||
// It should have started playing
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering, [.loading, .playing], timeout: 5)
|
||||
audioPlayer.load(item: Source.getAudioItem())
|
||||
|
||||
XCTAssertEqual(audioPlayer.items.count, 1)
|
||||
XCTAssertEqual(audioPlayer.currentItem?.getSourceUrl(), Source.getAudioItem().getSourceUrl())
|
||||
waitEqual(self.playerStateEventListener.statesWithoutBuffering.prefix(4), [.loading, .playing, .loading, .playing], timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Next
|
||||
|
||||
func testNextOnEmptyQueue() {
|
||||
audioPlayer.next()
|
||||
// should not have emitted a playbackEnd event
|
||||
XCTAssertNil(playbackEndEventListener.lastReason)
|
||||
}
|
||||
|
||||
func testNextWhenPaused() {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
// should go to previous item and not play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenPausedWithoutPlaying() {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.pause()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
// should go to previous item and not play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenPlaying() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
// should go to previous item and play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Previous
|
||||
|
||||
func testPreviousOnEmptyQueue() {
|
||||
audioPlayer.previous()
|
||||
// should not have emitted a playbackEnd event
|
||||
XCTAssertNil(playbackEndEventListener.lastReason)
|
||||
}
|
||||
|
||||
func testPreviousWhenPlaying() {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()], playWhenReady: true)
|
||||
audioPlayer.next()
|
||||
audioPlayer.previous()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.previousItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
// should go to previous item and play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: 5)
|
||||
}
|
||||
|
||||
func testPreviousWhenPaused() {
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.next()
|
||||
audioPlayer.pause()
|
||||
audioPlayer.previous()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.previousItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
// should go to previous item and not play
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ready, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Move
|
||||
|
||||
func testMoveItemsRepeatModeOff() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
|
||||
// Move the first (currently playing track) above the second and seek to near the end of the track
|
||||
try? audioPlayer.moveItem(fromIndex: 0, toIndex: 1)
|
||||
audioPlayer.repeatMode = RepeatMode.off
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.ended, timeout: 5)
|
||||
}
|
||||
|
||||
func testMoveItemsRepeatModeQueue() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
|
||||
// Move the first (currently playing track) above the second and seek to near the end of the track
|
||||
try? audioPlayer.moveItem(fromIndex: 0, toIndex: 1)
|
||||
audioPlayer.repeatMode = RepeatMode.queue
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, AudioPlayerState.playing, timeout: 5)
|
||||
}
|
||||
|
||||
func testMoveItemsRepeatModeTrack() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
|
||||
// Move the first (currently playing track) above the second and seek to near the end of the track
|
||||
try? audioPlayer.moveItem(fromIndex: 0, toIndex: 1)
|
||||
audioPlayer.repeatMode = RepeatMode.track
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitTrue(self.audioPlayer.currentTime < 4.6, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 0, timeout: 5)
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 1)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Off - Two Items)
|
||||
|
||||
func setupRepeatModeOffTests() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.repeatMode = .off
|
||||
}
|
||||
|
||||
func testTrackEndWhenRepeatModeOff() {
|
||||
setupRepeatModeOffTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
|
||||
// Allow final track to end
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
waitEqual(self.audioPlayer.currentTime, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.index, 1, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeOff() {
|
||||
setupRepeatModeOffTests()
|
||||
audioPlayer.play()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
|
||||
// Calling next on the final track
|
||||
audioPlayer.next()
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentTime, 5, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Track - Two Items)
|
||||
|
||||
func setupRepeatModeTrackTests() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.repeatMode = .track
|
||||
}
|
||||
|
||||
func testRestartTrackWhenRepeatModeTrack() {
|
||||
setupRepeatModeTrackTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeTrack() {
|
||||
setupRepeatModeTrackTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Repeat Mode (Queue - Two Items)
|
||||
|
||||
func setupRepeatModeQueueTests() {
|
||||
audioPlayer.play()
|
||||
audioPlayer.add(items: [FiveSecondSource.getAudioItem(), FiveSecondSource.getAudioItem()])
|
||||
audioPlayer.repeatMode = .queue
|
||||
}
|
||||
|
||||
func testSeekToEndWhenRepeatModeQueue() {
|
||||
setupRepeatModeQueueTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
|
||||
// Allow the final track to end
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 1, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeQueue() {
|
||||
setupRepeatModeQueueTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextTwiceWhenRepeatModeQueue() {
|
||||
setupRepeatModeQueueTests()
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 0)
|
||||
XCTAssertNil(currentItemEventListener.lastIndex)
|
||||
|
||||
audioPlayer.next()
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 1)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 0, timeout: 5)
|
||||
|
||||
audioPlayer.next()
|
||||
XCTAssertEqual(audioPlayer.currentIndex, 0)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Off - One Item)
|
||||
|
||||
func setupRepeatModeOffOneItemTests() {
|
||||
audioPlayer.add(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
audioPlayer.repeatMode = .off
|
||||
}
|
||||
|
||||
func testTrackEndWhenRepeatModeOffOneItem() {
|
||||
setupRepeatModeOffOneItemTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .ended, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeOffOneItem() {
|
||||
setupRepeatModeOffOneItemTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
// TODO: Test this more thoroughly?
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Track - One Item)
|
||||
|
||||
func setupRepeatModeTrackOneItemTests() {
|
||||
audioPlayer.add(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
audioPlayer.repeatMode = .track
|
||||
}
|
||||
|
||||
func testRestartTrackWhenRepeatModeTrackOneItem() {
|
||||
setupRepeatModeTrackOneItemTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitEqual(self.currentItemEventListener.lastIndex, nil, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeTrackOneItem() {
|
||||
setupRepeatModeTrackOneItemTests()
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.currentTime, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.nextItems.count, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
// MARK: - Repeat Mode (Queue - One Item)
|
||||
|
||||
func setupRepeatModeQueueOneItemTests() {
|
||||
audioPlayer.add(item: FiveSecondSource.getAudioItem(), playWhenReady: true)
|
||||
audioPlayer.repeatMode = .queue
|
||||
}
|
||||
|
||||
func testSeekToEndWhenRepeatModeQueueOneItem() {
|
||||
setupRepeatModeQueueOneItemTests()
|
||||
waitForSeek(audioPlayer, to: 4.6)
|
||||
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime > 4.5, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime < 1, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
|
||||
func testNextWhenRepeatModeQueueOneItem() {
|
||||
setupRepeatModeQueueOneItemTests()
|
||||
waitForSeek(audioPlayer, to: 2)
|
||||
audioPlayer.next()
|
||||
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
waitTrue(self.audioPlayer.currentTime < 1.9, timeout: 5)
|
||||
waitEqual(self.audioPlayer.currentIndex, 0, timeout: 5)
|
||||
waitEqual(self.audioPlayer.playerState, .playing, timeout: 5)
|
||||
}
|
||||
}
|
||||
|
||||
extension QueuedAudioPlayer {
|
||||
|
||||
class SeekEventListener {
|
||||
private let lockQueue = DispatchQueue(
|
||||
label: "SeekEventListener.lockQueue",
|
||||
target: .global()
|
||||
)
|
||||
var _eventResult: (Double, Bool) = (-1, false)
|
||||
var eventResult: (Double, Bool) {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
_eventResult
|
||||
}
|
||||
}
|
||||
}
|
||||
func handleEvent(seconds: Double, didFinish: Bool) {
|
||||
lockQueue.sync {
|
||||
_eventResult = (seconds, didFinish)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CurrentItemEventListener {
|
||||
private let lockQueue = DispatchQueue(
|
||||
label: "CurrentItemEventListener.lockQueue",
|
||||
target: .global()
|
||||
)
|
||||
var _item: AudioItem? = nil
|
||||
var _index: Int? = nil
|
||||
var _lastItem: AudioItem? = nil
|
||||
var _lastIndex: Int? = nil
|
||||
var _lastPosition: Double? = nil
|
||||
|
||||
var item: AudioItem? {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _item
|
||||
}
|
||||
}
|
||||
}
|
||||
var index: Int? {
|
||||
return lockQueue.sync {
|
||||
return _index
|
||||
}
|
||||
}
|
||||
var lastItem: AudioItem? {
|
||||
return lockQueue.sync {
|
||||
return _lastItem
|
||||
}
|
||||
}
|
||||
var lastIndex: Int? {
|
||||
return lockQueue.sync {
|
||||
return _lastIndex
|
||||
}
|
||||
}
|
||||
var lastPosition: Double? {
|
||||
return lockQueue.sync {
|
||||
return _lastPosition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func handleEvent(
|
||||
item: AudioItem?,
|
||||
index: Int?,
|
||||
lastItem: AudioItem?,
|
||||
lastIndex: Int?,
|
||||
lastPosition: Double?
|
||||
) {
|
||||
lockQueue.sync {
|
||||
_item = item
|
||||
_index = index
|
||||
_lastItem = lastItem
|
||||
_lastIndex = lastIndex
|
||||
_lastPosition = lastPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlaybackEndEventListener {
|
||||
private let lockQueue = DispatchQueue(
|
||||
label: "PlaybackEndEventListener.lockQueue",
|
||||
target: .global()
|
||||
)
|
||||
var _lastReason: PlaybackEndedReason? = nil
|
||||
var lastReason: PlaybackEndedReason? {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _lastReason
|
||||
}
|
||||
}
|
||||
}
|
||||
var _reasons: [PlaybackEndedReason] = []
|
||||
var reasons: [PlaybackEndedReason] {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _reasons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleEvent(reason: PlaybackEndedReason) {
|
||||
lockQueue.sync {
|
||||
_lastReason = reason
|
||||
_reasons.append(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerStateEventListener {
|
||||
private let lockQueue = DispatchQueue(
|
||||
label: "PlayerStateEventListener.lockQueue",
|
||||
target: .global()
|
||||
)
|
||||
var _states: [AudioPlayerState] = []
|
||||
var states: [AudioPlayerState] {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _states
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
lockQueue.sync {
|
||||
_states = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
private var _statesWithoutBuffering: [AudioPlayerState] = []
|
||||
var statesWithoutBuffering: [AudioPlayerState] {
|
||||
get {
|
||||
return lockQueue.sync {
|
||||
return _statesWithoutBuffering
|
||||
}
|
||||
}
|
||||
|
||||
set {
|
||||
lockQueue.sync {
|
||||
_statesWithoutBuffering = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
func handleEvent(state: AudioPlayerState) {
|
||||
states.append(state)
|
||||
if (state != .ready && state != .buffering && (statesWithoutBuffering.isEmpty || statesWithoutBuffering.last != state)) {
|
||||
statesWithoutBuffering.append(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import SwiftAudioEx
|
||||
|
||||
extension XCTestCase {
|
||||
func waitForSeek(_ audioPlayer: AudioPlayer, to time: Double) {
|
||||
let seekEventListener = QueuedAudioPlayer.SeekEventListener()
|
||||
audioPlayer.event.seek.addListener(seekEventListener, seekEventListener.handleEvent)
|
||||
audioPlayer.seek(to: time)
|
||||
|
||||
waitEqual(seekEventListener.eventResult.0, time, accuracy: 0.1, timeout: 5)
|
||||
waitEqual(seekEventListener.eventResult.1, true, timeout: 5)
|
||||
}
|
||||
|
||||
func waitTrue(_ expression: @autoclosure @escaping () -> Bool, timeout: TimeInterval) {
|
||||
let expectation = XCTestExpectation(description: "Value should eventually equal expected value")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
while !expression() {
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
}
|
||||
|
||||
func waitEqual<T: Equatable>(_ expression1: @autoclosure @escaping () -> T, _ expression2: @autoclosure @escaping () -> T, timeout: TimeInterval) {
|
||||
let expectation = XCTestExpectation(description: "Value should eventually equal expected value")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
while expression1() != expression2() {
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
}
|
||||
|
||||
func waitEqual<T: Equatable>(_ expression1: @autoclosure @escaping () -> T, _ expression2: @autoclosure @escaping () -> T, accuracy: T, timeout: TimeInterval) where T: FloatingPoint {
|
||||
let expectation = XCTestExpectation(description: "Value should eventually equal expected value with accuracy")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
let startTime = Date()
|
||||
while abs(expression1() - expression2()) > accuracy {
|
||||
if Date().timeIntervalSince(startTime) >= timeout {
|
||||
break
|
||||
}
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
return wait(for: [expectation], timeout: timeout)
|
||||
}
|
||||
|
||||
func waitEqual<T1: Equatable, T2: Equatable>(_ expression1: @autoclosure @escaping () -> (T1, T2), _ expression2: @autoclosure @escaping () -> (T1, T2), timeout: TimeInterval) {
|
||||
let expectation = XCTestExpectation(description: "Values should eventually be equal")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
while expression1() != expression2() {
|
||||
usleep(100_000) // Sleep for 100 milliseconds
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
wait(for: [expectation], timeout: timeout)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
//
|
||||
// Sources.swift
|
||||
// SwiftAudio_Tests
|
||||
//
|
||||
// Created by Jørgen Henrichsen on 05/08/2018.
|
||||
// Copyright © 2018 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftAudioEx
|
||||
import UIKit
|
||||
|
||||
struct Source {
|
||||
static let path: String = Bundle.main.path(forResource: "TestSound", ofType: "m4a")!
|
||||
static let path: String = Bundle.module.path(forResource: "TestSound", ofType: "m4a")!
|
||||
static let url: URL = URL(fileURLWithPath: Source.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
@@ -20,7 +12,7 @@ struct Source {
|
||||
}
|
||||
|
||||
struct ShortSource {
|
||||
static let path: String = Bundle.main.path(forResource: "ShortTestSound", ofType: "m4a")!
|
||||
static let path: String = Bundle.module.path(forResource: "ShortTestSound", ofType: "m4a")!
|
||||
static let url: URL = URL(fileURLWithPath: ShortSource.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
@@ -29,7 +21,7 @@ struct ShortSource {
|
||||
}
|
||||
|
||||
struct LongSource {
|
||||
static let path: String = Bundle.main.path(forResource: "WAV-MP3", ofType: "wav")!
|
||||
static let path: String = Bundle.module.path(forResource: "WAV-MP3", ofType: "wav")!
|
||||
static let url: URL = URL(fileURLWithPath: LongSource.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
@@ -38,7 +30,7 @@ struct LongSource {
|
||||
}
|
||||
|
||||
struct FiveSecondSource {
|
||||
static let path: String = Bundle.main.path(forResource: "five_seconds", ofType: "m4a")!
|
||||
static let path: String = Bundle.module.path(forResource: "five_seconds", ofType: "m4a")!
|
||||
static let url: URL = URL(fileURLWithPath: FiveSecondSource.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
@@ -47,7 +39,7 @@ struct FiveSecondSource {
|
||||
}
|
||||
|
||||
struct FiveSecondSourceWithInitialTimeOfFourSeconds {
|
||||
static let path: String = Bundle.main.path(forResource: "five_seconds", ofType: "m4a")!
|
||||
static let path: String = Bundle.module.path(forResource: "five_seconds", ofType: "m4a")!
|
||||
static let url: URL = URL(fileURLWithPath: FiveSecondSource.path)
|
||||
|
||||
static func getAudioItem() -> AudioItem {
|
||||
Reference in New Issue
Block a user