Compare commits

...

6 Commits

Author SHA1 Message Date
dcvz 7fb762db9c Bump to v1.0.0 2023-10-20 23:03:33 +02:00
dcvz 81ce63752d Bump version to 1.0.0-rc.11 2023-10-20 17:53:51 +02:00
David Chavez c03c83096e fix(crash): Fix crash on attaching metadata output when loading items too quickly (#68) 2023-10-20 17:49:14 +02:00
David Chavez 0c87d2479e chore(tests): revamp and move to library (#67) 2023-10-20 14:47:19 +02:00
dcvz 98f3646e84 Bump version to 1.0.0-rc.10 2023-10-19 16:15:15 +02:00
David Chavez 9ebbd99230 fix(notification): update rate in notification when rate changes (#66) 2023-10-19 16:13:16 +02:00
70 changed files with 2390 additions and 3396 deletions
+1 -2
View File
@@ -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;
@@ -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)
}
}
-86
View File
@@ -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())
}
}
}
}
}
}
-81
View File
@@ -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))
}
}
}
}
}
}
}
-590
View File
@@ -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
}
}
-24
View File
@@ -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())
}
}
}
}
}
}
}
-73
View File
@@ -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())
}
}
}
}
}
}
-673
View File
@@ -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
View File
@@ -13,7 +13,13 @@ let package = Package(
targets: [
.target(
name: "SwiftAudioEx",
dependencies: [],
path: "SwiftAudioEx/Classes")
dependencies: []),
.testTarget(
name: "SwiftAudioExTests",
dependencies: ["SwiftAudioEx"],
resources: [
.process("Resources")
]
),
]
)
+13 -14
View File
@@ -1,12 +1,11 @@
![logo](Images/original-horizontal.png)
# SwiftAudio
# SwiftAudioEx
[![codecov](https://codecov.io/gh/DoubleSymmetry/SwiftAudio/branch/master/graph/badge.svg?token=FD5THGSHM5)](https://codecov.io/gh/DoubleSymmetry/SwiftAudio)
[![License](https://img.shields.io/cocoapods/l/SwiftAudio.svg?style=flat)](http://cocoapods.org/pods/SwiftAudio)
[![Platform](https://img.shields.io/cocoapods/p/SwiftAudio.svg?style=flat)](http://cocoapods.org/pods/SwiftAudio)
[![License](https://img.shields.io/cocoapods/l/SwiftAudioEx.svg?style=flat)](http://cocoapods.org/pods/SwiftAudioEx)
[![Platform](https://img.shields.io/cocoapods/p/SwiftAudioEx.svg?style=flat)](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
@@ -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
}
+2 -2
View File
@@ -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
View File
View File
@@ -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)
}
}
@@ -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 {